diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..beeb1f2
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,7 @@
+# This is a basic workflow to help you get started with Actions
+
+name: CI
+
+permissions:
+ contents: write
+ pull-requests: write
diff --git a/.github/workflows/notify-mh.yml b/.github/workflows/notify-mh.yml
new file mode 100644
index 0000000..6b00603
--- /dev/null
+++ b/.github/workflows/notify-mh.yml
@@ -0,0 +1,37 @@
+# MH relies on its own version of the playbooks, located in cc-deployment-private.
+# Despite this, MH still wants to be informed of any changes that occur in this
+# repo incase it is relevant to their hosting needs. For example, MH will probably
+# need to port over changes made in roles/mysql-config/**, but they don't need
+# changes in roles/mysql-local/**.
+name: Inform MH About -public Changes
+on:
+ pull_request_target:
+ types: [closed]
+ branches:
+ - 'main'
+ paths:
+ - 'roles/tomcat/**'
+ - 'roles/cc-scorm-engine/**'
+ - 'roles/common/**'
+ - 'roles/content-controller/**'
+ - 'roles/mnt/**'
+ - 'roles/mysql-config/**'
+ - 'roles/tomcat/**'
+ - 'roles/java/**'
+ - 'roles/saml/**'
+ - 'roles/ssl/**'
+ - 'build_ami.yml'
+ - 'env.yml'
+
+jobs:
+ notify-slack:
+ if: github.event.pull_request.merged == true && !startsWith(github.head_ref, 'main')
+ runs-on: self-hosted
+ steps:
+ - name: Send Slack notification
+ uses: rtCamp/action-slack-notify@v2
+ env:
+ SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
+ SLACK_CHANNEL: rustici-ops
+ SLACK_USERNAME: cc-deployment-public
+ SLACK_MESSAGE: ':eyes: Someone pushed changes to cc-deployment-public that may be relevant to cc-deployment-private :eyes: ${{ github.event.pull_request._links.html.href }}'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f70e0bb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,74 @@
+# cc Specific config files
+group_vars/content_controller.yml
+group_vars/engine_java.yml
+group_vars/env.yml
+group_vars/s3.yml
+group_vars/aws.yml
+group_vars/cloudfront.yml
+group_vars/aws.yml
+group_vars/keypair.yml
+group_vars/test_hosts.yml
+roles/users
+roles/ssl/files/*
+roles/content-controller/files/*
+roles/cloudfront/files/*
+roles/logstash-filebeat
+roles/dripstat
+roles/site24x7
+roles/scorm-engine-2016
+roles/apache-engine-2016
+roles/ssl/files
+roles/minio
+host_vars/*.scorm.com*.yml
+host_vars/*.yml
+roles/aws-s3/files/*.json
+*.retry
+engine.yml
+run-multi-os-test.yml
+cleanup-multi-os-test.yml
+.idea/
+build_ami_engine2016.yml
+roles/consul
+roles/doc
+roles/mocha-api
+roles/newrelic-infrastructure
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+.vagrant/
+Vagrantfile
+cc-dev.yml
+create-test-stack.yml
+destroy-test-stack.yml
+env-dev.yml
+build-pita-worker-ami.yml
+roles/qa-dev
+
+# Files generated by Hugo during the build
+doc/resources/_gen
+doc/public
+additional_doc/public
+additional_doc/resources
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..e69de29
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..932a768
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,5 @@
+# Rustici Content Controller
+
+## Ansible Deployment Playbooks
+
+Copyright 2018 Rustici Software, LLC. All Rights Reserved.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8509b05
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+![RusticiContentControllerLogo](img/Rustici_ContentController.png)
+
+[Please refer to the official deployment documentation available here.](https://docs.contentcontroller.com/)
+
diff --git a/Vagrant-README.md b/Vagrant-README.md
new file mode 100644
index 0000000..e082557
--- /dev/null
+++ b/Vagrant-README.md
@@ -0,0 +1,39 @@
+![RusticiContentControllerLogo](img/Rustici_ContentController.png)
+
+## Vagrant
+
+Vagrant can be useful for running test environments. We provide preconfigured Vagrantfiles for Parallels. You'll need Vagrant 1.8+ for Parallels to behave on El Capitan.
+
+[To install Vagrant, head to https://www.vagrantup.com/downloads.html](https://www.vagrantup.com/downloads.html)
+
+After installing Vagrant, you'll need to ensure that you have the `hostmanager` plugin installed:
+
+ vagrant plugin install vagrant-hostmanager
+
+There are two VMs enumerated in this Vagrantfile, each of which responds to different DNS names:
+
+_cc.example.com_
+ cc # Runs the cc environment without s3 support
+
+_ccs3.example.com_
+ ccs3 # Runs the cc environment WITH s3 support
+
+Most of the time, you'll prolly want to use "cc".
+
+To use Parallels, do:
+
+ ln -s Vagrantfile.parallels Vagrantfile
+
+ vagrant plugin install vagrant-parallels
+
+ vagrant up # Launches both S3-enabled and local storage VMs (you'll need 8GB free to make this go.)
+
+ vagrant up cc # Runs the cc environment without s3 support
+
+ vagrant up ccs3 # Runs the cc environment WITH s3 support
+
+To ssh to your box, you need to specify a host. Hosts are defined in the Vagrantfile
+
+ vagrant ssh cc # ssh to the box with local storage
+
+ vagrant ssh ccs3 # ssh to the box with S3 Storage
diff --git a/Vagrantfile.parallels b/Vagrantfile.parallels
new file mode 100644
index 0000000..6a80d31
--- /dev/null
+++ b/Vagrantfile.parallels
@@ -0,0 +1,190 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# All Vagrant configuration is done below. The "2" in Vagrant.configure
+# configures the configuration version (we support older styles for
+# backwards compatibility). Please don't change it unless you know what
+# you're doing.
+
+Vagrant.configure(2) do |config|
+ # The most common configuration options are documented and commented below.
+ # For a complete reference, please see the online documentation at
+ # https://docs.vagrantup.com.
+ config.hostmanager.enabled = true
+ config.hostmanager.manage_host = true
+ config.hostmanager.manage_guest = true
+ config.hostmanager.ignore_private_ip = false
+ config.hostmanager.include_offline = true
+
+ config.vm.define "cc" do |cc|
+
+ # Every Vagrant development environment requires a box. You can search for
+ # boxes at https://app.vagrantup.com/boxes/search.
+ cc.vm.box = "ilker/ubuntu2004"
+
+ # Disable automatic box update checking. If you disable this, then
+ # boxes will only be checked for updates when the user runs
+ # `vagrant box outdated`. This is not recommended.
+ # cc.vm.box_check_update = false
+
+ # Create a forwarded port mapping which allows access to a specific port
+ # within the machine from a port on the host machine. In the example below,
+ # accessing "localhost:8080" will access port 80 on the guest machine.
+ # cc.vm.network "forwarded_port", guest: 80, host: 8080
+
+ cc.vm.hostname = "local-dev"
+ cc.vm.network "private_network", ip: "192.168.57.2"
+ cc.vm.network "private_network", type: "dhcp"
+ cc.hostmanager.aliases = %w(cc.example.com, contentportal.example.com)
+
+ # Create a public network, which generally matched to bridged network.
+ # Bridged networks make the machine appear as another physical device on
+ # your network.
+ # cc.vm.network "public_network"
+
+ # Share an additional folder to the guest VM. The first argument is
+ # the path on the host to the actual folder. The second argument is
+ # the path on the guest to mount the folder. And the optional third
+ # argument is a set of non-required options.
+ # cc.vm.synced_folder "deploy", "/deploy"
+
+
+ #cc.vm.provision :shell, path: "bootstrap.sh", run: "once"
+
+ cc.vm.provider "parallels" do |pr|
+ pr.cpus = "2"
+ # Customize the amount of memory on the VM:
+ pr.memory = "4096"
+ end
+
+ cc.vm.provision :ansible do |ansible|
+ ansible.playbook = "env-dev.yml"
+ ansible.verbose = "v"
+ ansible.extra_vars = "@host_vars/cc.example.com.yml"
+ ansible.raw_arguments = "-e cc_version=4.0"
+ end
+
+ end
+
+ config.vm.define "cc-1-2" do |cc|
+
+ # Every Vagrant development environment requires a box. You can search for
+ # boxes at https://atlas.hashicorp.com/search.
+ cc.vm.box = "ilker/ubuntu2004"
+
+ cc.vm.hostname = "local-dev"
+ cc.vm.network "private_network", ip: "192.168.88.23"
+ cc.vm.network "private_network", type: "dhcp"
+ cc.hostmanager.aliases = %w(cc-1-2.example.com)
+
+ cc.vm.provider "parallels" do |pr|
+ pr.cpus = "2"
+ # Customize the amount of memory on the VM:
+ pr.memory = "4096"
+ end
+
+ cc.vm.provision :ansible do |ansible|
+ ansible.playbook = "env.yml"
+ ansible.verbose = "v"
+ ansible.extra_vars = { ServerName: "cc-1-2.example.com", S3FileStorageEnabled: false, use_ssl: true, build_name: "ContentController-1.2.317" }
+ ansible.raw_arguments = "-e cc_version=4.0"
+ end
+
+ end
+
+ config.vm.define "cc-cent" do |cc|
+
+ # Every Vagrant development environment requires a box. You can search for
+ # boxes at https://atlas.hashicorp.com/search.
+ cc.vm.box = "parallels/centos-7.2"
+
+ # Disable automatic box update checking. If you disable this, then
+ # boxes will only be checked for updates when the user runs
+ # `vagrant box outdated`. This is not recommended.
+ # cc.vm.box_check_update = false
+
+ # Create a forwarded port mapping which allows access to a specific port
+ # within the machine from a port on the host machine. In the example below,
+ # accessing "localhost:8080" will access port 80 on the guest machine.
+ # cc.vm.network "forwarded_port", guest: 80, host: 8080
+
+ cc.vm.hostname = "cc-cent"
+ cc.vm.network "private_network", type: "dhcp"
+ cc.hostmanager.aliases = %w(cc-cent.rusticisoftware.com)
+
+ # Create a public network, which generally matched to bridged network.
+ # Bridged networks make the machine appear as another physical device on
+ # your network.
+ # cc.vm.network "public_network"
+
+ # Share an additional folder to the guest VM. The first argument is
+ # the path on the host to the actual folder. The second argument is
+ # the path on the guest to mount the folder. And the optional third
+ # argument is a set of non-required options.
+ # cc.vm.synced_folder "deploy", "/deploy"
+
+
+ #cc.vm.provision :shell, path: "bootstrap.sh", run: "once"
+
+ cc.vm.provider "parallels" do |pr|
+ pr.cpus = "2"
+ # Customize the amount of memory on the VM:
+ pr.memory = "4096"
+ end
+
+ cc.vm.provision :ansible do |ansible|
+ ansible.playbook = "cc-cent.yml"
+ ansible.verbose = "v"
+ end
+
+ end
+
+ config.vm.define "se" do |cc|
+
+ # Every Vagrant development environment requires a box. You can search for
+ # boxes at https://atlas.hashicorp.com/search.
+ cc.vm.box = "ilker/ubuntu2004"
+
+ # Disable automatic box update checking. If you disable this, then
+ # boxes will only be checked for updates when the user runs
+ # `vagrant box outdated`. This is not recommended.
+ # cc.vm.box_check_update = false
+
+ # Create a forwarded port mapping which allows access to a specific port
+ # within the machine from a port on the host machine. In the example below,
+ # accessing "localhost:8080" will access port 80 on the guest machine.
+ # cc.vm.network "forwarded_port", guest: 80, host: 8080
+
+ cc.vm.hostname = "local-dev"
+ cc.vm.network "private_network", type: "dhcp"
+ cc.hostmanager.aliases = %w(dev.engine.scorm.com)
+
+ # Create a public network, which generally matched to bridged network.
+ # Bridged networks make the machine appear as another physical device on
+ # your network.
+ # cc.vm.network "public_network"
+
+ # Share an additional folder to the guest VM. The first argument is
+ # the path on the host to the actual folder. The second argument is
+ # the path on the guest to mount the folder. And the optional third
+ # argument is a set of non-required options.
+ # cc.vm.synced_folder "deploy", "/deploy"
+
+
+ #cc.vm.provision :shell, path: "bootstrap.sh", run: "once"
+
+ cc.vm.provider "parallels" do |pr|
+ pr.cpus = "2"
+ # Customize the amount of memory on the VM:
+ pr.memory = "2048"
+ end
+
+ cc.vm.provision :ansible do |ansible|
+ ansible.playbook = "engine.yml"
+ ansible.verbose = "v"
+ ansible.extra_vars = "@host_vars/dev.engine.scorm.com.yml"
+ end
+
+ end
+
+end
diff --git a/Vagrantfile.virtualbox b/Vagrantfile.virtualbox
new file mode 100644
index 0000000..dacfa19
--- /dev/null
+++ b/Vagrantfile.virtualbox
@@ -0,0 +1,69 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# All Vagrant configuration is done below. The "2" in Vagrant.configure
+# configures the configuration version (we support older styles for
+# backwards compatibility). Please don't change it unless you know what
+# you're doing.
+
+Vagrant.configure(2) do |config|
+ # The most common configuration options are documented and commented below.
+ # For a complete reference, please see the online documentation at
+ # https://docs.vagrantup.com.
+ config.hostmanager.enabled = true
+ config.hostmanager.manage_host = true
+ config.hostmanager.manage_guest = true
+ config.hostmanager.ignore_private_ip = false
+ config.hostmanager.include_offline = true
+
+ config.vm.define "cc" do |cc|
+
+ # Every Vagrant development environment requires a box. You can search for
+ # boxes at https://app.vagrantup.com/boxes/search.
+ cc.vm.box = "ubuntu/focal64"
+
+ # Disable automatic box update checking. If you disable this, then
+ # boxes will only be checked for updates when the user runs
+ # `vagrant box outdated`. This is not recommended.
+ # cc.vm.box_check_update = false
+
+ # Create a forwarded port mapping which allows access to a specific port
+ # within the machine from a port on the host machine. In the example below,
+ # accessing "localhost:8080" will access port 80 on the guest machine.
+ # cc.vm.network "forwarded_port", guest: 80, host: 8080
+
+ cc.vm.hostname = "local-dev"
+ cc.vm.network "private_network", ip: "192.168.57.2"
+ cc.vm.network "private_network", type: "dhcp"
+ cc.hostmanager.aliases = %w(cc.example.com, contentportal.example.com)
+
+ # Create a public network, which generally matched to bridged network.
+ # Bridged networks make the machine appear as another physical device on
+ # your network.
+ # cc.vm.network "public_network"
+
+ # Share an additional folder to the guest VM. The first argument is
+ # the path on the host to the actual folder. The second argument is
+ # the path on the guest to mount the folder. And the optional third
+ # argument is a set of non-required options.
+ # cc.vm.synced_folder "deploy", "/deploy"
+
+
+ #cc.vm.provision :shell, path: "bootstrap.sh", run: "once"
+
+ cc.vm.provider "virtualbox" do |pr|
+ pr.cpus = "2"
+ # Customize the amount of memory on the VM:
+ pr.memory = "4096"
+ end
+
+ cc.vm.provision :ansible do |ansible|
+ ansible.playbook = "env.yml"
+ ansible.verbose = "v"
+ ansible.extra_vars = "@host_vars/cc.example.com.yml"
+ ansible.raw_arguments = "-e cc_version=3.3 -e use_s3_release=true -e build_name=ContentController-3.3.159 --extra-vars='@../cc-deployment-dev/group_vars/keypair.yml'"
+ end
+
+ end
+
+end
diff --git a/additional_doc/.gitmodules b/additional_doc/.gitmodules
new file mode 100644
index 0000000..d265c02
--- /dev/null
+++ b/additional_doc/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "themes/rustici-docs-hugo-theme"]
+ path = themes/rustici-docs-hugo-theme
+ url = https://github.com/RusticiSoftware/rustici-docs-hugo-theme.git
\ No newline at end of file
diff --git a/additional_doc/README.md b/additional_doc/README.md
new file mode 100644
index 0000000..18f7141
--- /dev/null
+++ b/additional_doc/README.md
@@ -0,0 +1,19 @@
+To install Hugo, run `./install.sh`.
+
+To run the Hugo docs locally, run `hugo server -D`.
+
+To generate the static files, just run `hugo` from this directory. Then all of the static files will be generated into
+the `public` directory.
+
+To change the theme or add a new submodule run
+```bash
+cd themes
+git submodule add https://github.com/RusticiSoftware/rustici-docs-hugo-theme.git
+```
+
+If you face an issue where the integration docs refuse to build, it may be because the submodule is missing. This
+could happen because of re-cloning, forking, or dark magic. To fix this issue, run the following in `additional_doc`:
+```
+git submodule init
+git submodule update
+```
\ No newline at end of file
diff --git a/additional_doc/archetypes/default.md b/additional_doc/archetypes/default.md
new file mode 100644
index 0000000..00e77bd
--- /dev/null
+++ b/additional_doc/archetypes/default.md
@@ -0,0 +1,6 @@
+---
+title: "{{ replace .Name "-" " " | title }}"
+date: {{ .Date }}
+draft: true
+---
+
diff --git a/additional_doc/config.yaml b/additional_doc/config.yaml
new file mode 100644
index 0000000..bcc36bf
--- /dev/null
+++ b/additional_doc/config.yaml
@@ -0,0 +1,17 @@
+baseURL: "https://docs.contentcontroller.com/integration/"
+languageCode: "en-us"
+title: "Content Controller - Integrations"
+theme: "rustici-docs-hugo-theme"
+
+menu:
+ main:
+ - identifier: cc_content
+ name: "Content"
+ weight: 6
+
+ - identifier: features
+ name: "Features"
+ weight: 7
+
+params:
+ logo: '/integration/images/CC_Rustici_landscape_white.svg'
diff --git a/additional_doc/content/_index.md b/additional_doc/content/_index.md
new file mode 100644
index 0000000..81ac1db
--- /dev/null
+++ b/additional_doc/content/_index.md
@@ -0,0 +1,22 @@
+---
+title: "Introduction"
+type: docs
+menu: "main"
+---
+
+## Content Controller Integrations
+
+Welcome! This documentation covers additional features, security settings and in-depth knowledge for Content Controller.
+If you are looking for information on how best to utilize Content Controller that can be found in the
+[User Guide](https://guide.contentcontroller.com/).
+
+This documentation is meant as a reference for both self and managed hosted customers. Wherever changes to the
+Ansible playbooks are mentioned, managed hosted customers will need to reach out to support@scorm.com for assistance.
+
+## Getting Support
+
+While looking through this additional documentation, please remember that we are here to help!
+Team Delight is all about finding ways to make things work for you - don't hesitate to give us a call if you have
+questions or need advice!
+
+Email: support@scorm.com Phone: 866.497.2676
diff --git a/additional_doc/content/cc_content/content-proxy.md b/additional_doc/content/cc_content/content-proxy.md
new file mode 100644
index 0000000..c704fa1
--- /dev/null
+++ b/additional_doc/content/cc_content/content-proxy.md
@@ -0,0 +1,48 @@
+---
+title: "Content Proxy"
+type: docs
+menu:
+ main:
+ name: Content Proxy
+ identifier: content_proxy
+ parent: cc_content
+ weight: 5
+---
+
+# Content Proxy
+
+Some self-hosted customers need to authenticate their content that is hosted in S3, but can't use CloudFront.
+For those customers, we offer a CloudFront replacement that we call Content Proxy.
+
+It acts like a CDN, but it just lives on the CC app server(s). Content Controller has Apache configured to forward
+content requests to the Content Proxy service, which is hosted locally. The proxy can either rely on cookies or it can
+use Content-Vault.
+
+The Content Proxy is built independently of Content Controller. Reach out to Team Delight for assistance getting this
+build.
+
+There a few steps involved in configuring the Content Proxy. Firstly, you'll need to set up some Ansible variables in
+your playbooks:
+
+```
+# S3 settings
+S3FileStorageEnabled: true
+S3AWSAccountID: 1234567890
+S3FileStorageIAMUsername: "s3-user-name"
+S3FileStorageAwsId: "IAM access key ID here"
+S3FileStorageAwsKey: "IAM secret access key here"
+S3FileStorageBucket: "content-controller-bucket-name"
+S3FileStorageRegion: "us-east-1"
+
+# "CloudFront" settings (but not really)
+use_cloudfront: true
+cloudfront_access_key_id: ""
+cloudfront_origin_access_identity: ""
+
+# Content Proxy
+content_proxy_enabled: true
+enable_contentvault: false
+```
+
+Once deployed, you can verify that the Content Proxy is running as expected by SSHing onto the app server and checking
+the output of `sudo systemctl status content-proxy`.
diff --git a/additional_doc/content/cc_content/content-vault.md b/additional_doc/content/cc_content/content-vault.md
new file mode 100644
index 0000000..a8c10d5
--- /dev/null
+++ b/additional_doc/content/cc_content/content-vault.md
@@ -0,0 +1,47 @@
+---
+title: "Content Vault"
+type: docs
+menu:
+ main:
+ name: Content Vault
+ identifier: content_vault
+ parent: cc_content
+ weight: 5
+---
+
+# Content Vault
+
+Recently, browsers have been getting stricter about the cookies that websites can set for users. Since CC is generally
+loaded inside of an iframe, browsers think that we are trying to monitor users with our cookies, so our cookies get
+blocked. Previously, we were using these cookies to authenticate access to our content, but once the cookies started to
+be blocked, we had to come up with an alternate solution. "Content Vault" is the term we use to refer to this layer of
+cookieless content authentication.
+
+When a user launches the course, we will generate a unique URL path prefix with which they can access that course. This
+unique URL is only valid for a few hours (this time limit is configurable). In addition to that path, we can also record
+the IP address and/or browser User-Agent when the content is launched (this validation is also configurable). Then, on
+subsequent launches using the generated path, we will check that the requester's IP address and/or User-Agent is the
+same as the one that was used to launch the course.
+
+Here is an example content-vaulted path:
+
+```
+https://my-example.contentcontroller.com/vault/87a24c13-dc0b-450a-940c-f3efb2133ec7/courses/7e480c43-45fd-48a0-8aba-d77a965ab052/0/shared/launchpage.html
+```
+
+Everything after `/courses/` points to the actual file in S3, while the `/vault/87a24c13-dc0b-450a-940c-f3efb2133ec7` is
+the unique URL path prefix. This GUID is randomly generated on launch, and a new GUID is for every launch after that. We
+store this guid in a database table named `content_vault`.
+
+This table records the User-Agent and IP address from the launch request and saves them to this table, alongside the
+access GUID. For every request that includes this `/vault/` prefix, we verify that the details of the request for
+content match the details of the original launch request.
+
+We perform this verification using CloudFront and Lambda@Edge, which are two AWS technologies. CloudFront receives the
+request, sees the `/vault/` prefix, and then calls our Lambda@Edge function. This function analyzes the incoming content
+request and asks Content Controller to verify that the details look right.
+
+Content Controller checks the provided details, compares them to the values in `content_vault`. If everything matches
+up, Content Controller returns a successful response. To avoid overloading CC with these requests, successful
+verification results are cached for a short time. That way, the next time Lambda tries to verify a request, it just
+reads the cached value. This cache only lasts as long as the access GUID remains valid.
\ No newline at end of file
diff --git a/additional_doc/content/dispatch.md b/additional_doc/content/dispatch.md
new file mode 100644
index 0000000..805f13b
--- /dev/null
+++ b/additional_doc/content/dispatch.md
@@ -0,0 +1,13 @@
+---
+title: "Dispatch"
+type: docs
+menu:
+ main:
+ name: Dispatch Data Flow
+ identifier: dispatch
+ weight: 3
+---
+
+# Dispatch Data Flow
+
+The Rustici Software Security Documentation has an excellent section on the dispatch data flow along with a [dispatch data flow diagram](https://security.rusticisoftware.com/diagrams.html#application-data-flow-diagram-dispatch).
diff --git a/additional_doc/content/features/customization.md b/additional_doc/content/features/customization.md
new file mode 100644
index 0000000..277b256
--- /dev/null
+++ b/additional_doc/content/features/customization.md
@@ -0,0 +1,23 @@
+---
+title: "Customization"
+type: docs
+menu:
+ main:
+ name: Customization
+ identifier: customization
+ parent: features
+ weight: 5
+---
+
+# Customization
+
+## Favicons
+
+Content Controller's favicons can be customized with a `.png` file that you provide. The '16x16' format is the favicon that is likely to appear in a browsers tab, while the '32x32' format is likely to be used for browser shortcuts. We allow one or both of these files to be customized.
+
+To change the favicons:
+1. Add a new directory named `files` to `roles/content-controller`.
+2. In `files`, add the desired `.png` file with the name of `favicon-32x32.png` and/or `favicon-16x16.png`, depending on which favicon you wish to customize. The path to the new favicon(s) should be `roles/content-controller/files/favicon-##x##.png`.
+3. Redeploy
+
+Note: You may need to clear your browser's cache to see the new favicon.
\ No newline at end of file
diff --git a/additional_doc/content/features/import-tool.md b/additional_doc/content/features/import-tool.md
new file mode 100644
index 0000000..bd5924c
--- /dev/null
+++ b/additional_doc/content/features/import-tool.md
@@ -0,0 +1,40 @@
+---
+title: "The Bulk Import Tool"
+type: docs
+menu:
+ main:
+ name: Import Tool
+ identifier: import_tool
+ parent: features
+ weight: 5
+---
+
+# The Bulk Import Tool
+
+## What Is The Bulk Import Tool
+
+The Bulk Import Tool is a simple command-line application. You configure it, point it at a directory, and it will go through that directory, importing all of the courses it can find into your CC installation.
+
+Unlike the main application, we don't persist the versions of this tool in S3. Instead, once you've made your changes, just grab the new version of out the build directory (`~/cc/ContentController/service/contentcontroller-import/build/distributions`). We include both Unix and Windows versions of the tool in the same zip.
+
+## Common Issues
+
+The most common customer issue happens when trying to import courses that are too large, and the import tool runs out of heap space.
+
+This is a pretty easy fix. Just perform the following steps:
+
+For the Windows version of the tool:
+1. Open file `bin/contentcontroller-import.bat`
+2. Find the line `set DEFAULT_JVM_OPTS=` (should be around line 17).
+3. Update that line so it says `set DEFAULT_JVM_OPTS=-Xmx1g`*
+4. Save the file and rerun the tool.
+
+For the Unix version:
+1. Open file `bin/contentcontroller-import`
+2. Find the line `DEFAULT_JVM_OPTS=` (should be around line 31).
+3. Update that line so it says `DEFAULT_JVM_OPTS=-Xmx1g`*
+4. Save the file and rerun the tool.
+
+This is setting the maximum heap size of the application to 1 GB. To increase this value, replace the `1g` with something else. `500m` would be 500 MB, `2g` would be 2 GB, etc. Be careful here; if you're running a 32-bit version of Java, then you won't be able to go over ~1.5 GB of heap space.
+
+If you are still having trouble after increasing the heap size, then you can limit the tool's concurrency. Usually, the tool will have multiple threads going at once, since we spend so much time waiting for the server to respond to the import requests. If we limit this concurrency, we can lower the memory requirements of the tool. To do this, open the `contentcontroller-import.yaml` config file and change the value of the `threads` setting from `4` to `1`.
\ No newline at end of file
diff --git a/additional_doc/content/features/launcher-links.md b/additional_doc/content/features/launcher-links.md
new file mode 100644
index 0000000..00fd894
--- /dev/null
+++ b/additional_doc/content/features/launcher-links.md
@@ -0,0 +1,62 @@
+---
+title: "CC Launcher Links"
+type: docs
+menu:
+ main:
+ name: CC Launcher Links
+ identifier: launcher_links
+ parent: features
+ weight: 5
+---
+
+# CC Launcher
+
+## How It Works
+Launcher Links allow content to be shared via a URL link without the use of an LMS. When a user shares the URL link the
+hosted package makes a call back to their Content Controller instance. The Rustici SCORM Adapter within CC builds a
+request url that calls the launch API. The launch API verifies that the package token is associated with the content
+token. Upon verification, it returns the relevant dispatch information needed to fill out the template dispatch within
+the hosted package. The Rustici SCORM Adapter will then start the content in the player.
+
+## How To Use A Launcher Link
+This documentation will cover using a launcher link to successfully play a course. For the sake of brevity the UI
+instructions are included here. Check [Content Controller Automation API](https://docs.contentcontroller.com/automation-api/v1/)
+for the API calls. The flow will be similar, if not the same, as the UI use case. The major area where the UI and API
+use cases will overlap is setting up the launcher link package on a web server environment.
+
+### Enable The Feature
+This feature is an additional feature that is not included in the base Content Controller. Contact a really cool sales
+person to find out how to add this feature to your instance of Content Controller.
+
+### Create A Launcher Link Package
+The launcher link package contains the content player. This is the web application the you need to host on your server.
+Each account can create launcher link packages. The package is based on Rustici SCORM Adapter; an LMS adapter that
+allows SCORM content to be played. The package structure needs to be maintained when it is unzipped. Everything in the
+unzipped package must be web-accessible relative to `index.html`.
+
+To create a launcher link package in the UI, go to the Launcher Link option under an account's Advanced settings then
+select "Add". Enter a name for the package and the URL where the package will be hosted (this includes the package name).
+Select "Add Link".
+
+Once the launcher link package is created it can be disabled, enabled, or deleted. In order to delete the package it
+must be disabled first.
+
+### Host The Package
+Unzip the package on your web server. As mentioned earlier, make sure to leave the package structure as-is. There are a
+couple items in `config.js` that can be customized. Currently, these are `studentId` and `studentName`. The template
+`config.js` includes an example of how to generate a UUID as the learner ID for each time the content is accessed.
+
+### Share Content With A Launcher Link
+We currently support sharing individual pieces of content. For instance, an individual course within a bundle can be
+shared but not the whole bundle. Sharing content is as simple as selecting share on the content and finding the launcher
+link package name in the drop-down. Then select "Get Link". The URL that shows up is the launcher link URL meant to be
+shared with whomever is allowed to take the course. It has a unique token that identifies which course the hosted player
+should play.
+
+### Items Of Note
+* The dispatch offering up the content can still be limited by licensing and course deactivation.
+* Access to the content can be removed by disabling the launcher link package. This is in addition to the more
+ traditional CC methods.
+* In the package, `index.html` contains the package token, `ccPackageToken`, used to verify that the correct package is
+ attempting to play the content.
+* Currently unique identification for learners is the responsibility of the hosted package.
\ No newline at end of file
diff --git a/additional_doc/content/features/lead-in-page.md b/additional_doc/content/features/lead-in-page.md
new file mode 100644
index 0000000..0d642f8
--- /dev/null
+++ b/additional_doc/content/features/lead-in-page.md
@@ -0,0 +1,97 @@
+---
+title: "Lead-in Page"
+type: docs
+menu:
+ main:
+ name: Lead-in Page
+ identifier: lead_in_page
+ parent: features
+ weight: 5
+---
+
+# Lead-In Page
+
+## What Is A Lead-in Page
+A Lead-in Page is a self-hosted webpage that will precede content during the launch process. After the learner has
+viewed and interacted with the Lead-in Page, they will be returned to their content. A Lead-in Page can be configured
+with a GET or POST endpoint.
+
+## What Is Supported
+Displaying a Lead-in Page as part of the launch process is supported for Content, Equivalents, Bundled Content and
+Bundled Equivalents. A Lead-in page does _not_ support singular or Bundled RXD Content. The Lead-in Page is also _not_
+supported within the launch process for Launcher Links.
+
+## Setting Up A Lead-in Page
+Either endpoint setup requires the user to provide the location of the webpage that Content Controller will display. The
+Lead-in page will be displayed within an iframe. Content Controller uses that iframe to "listen" for the Lead-in page's
+notice to move on to the course content. The webpage will also need to include a simple script that notifies Content
+Controller.
+
+Script for the webpage:
+```js
+window.parent.postMessage({
+ 'message': ""
+}, "*");
+```
+
+### Where To Enable A Lead-In Page
+Users can choose to enable one Lead-in page for their entire instance of Content Controller or enable the page by
+account. To set an instance wide Lead-in page to display before any dispatch go to Administration > Settings > Launch >
+Lead-in Page. Whatever is set here can be overridden at the account level. Dispatches from that account will use that
+account's Lead-in page. The account level settings can be found in the account's Advanced Settings under the Lead-in
+Page tab.
+
+In the event that a user needs to use the Administration level Lead-in page settings but they've already set a separate
+Lead-in page for the account, the user can check "Overwrite with Admin Settings". Once the changes are saved that
+account will switch to using the Administration level Lead-in page for all dispatches.
+
+### GET Settings
+The GET option tells Content Controller to grab the Lead-in page, place it in an iframe and then wait for a message to
+move on to the course content. Once the Lead-in page settings are saved, those changes take
+effect immediately.
+
+Steps to add a GET style Lead-in page from the Account or Administration level:
+1. Choose GET from the _Select Endpoint Type_ dropdown.
+2. Fill in the _GET Endpoint URL_ with the URL location of the hosted Lead-in page.
+3. To start displaying the Lead-in page in front of any launched dispatch check _Display Lead-in page_.
+4. Save the settings.
+
+### POST Settings
+The POST option tells Content Controller to POST a JWT token to the given endpoint. Content Controller will expect to
+receive a response with a status code of 3xx and a `Location` header set to the URL of the Lead-in Page. Content
+Controller will use the given URL to redirect the learner. Once the learner has been redirected to the Lead-in page,
+Content Controller will rely on the Lead-in page to notify Content Controller when the learner needs to be redirected to
+the course. Content Controller will also handle a 2xx response as an indication to continue to the content launch and
+not attempt a Lead-in page redirect.
+
+Steps to add a POST style Lead-in page from the Account or Administration level:
+1. Choose POST from the _Select Endpoint Type_ dropdown.
+2. Fill in the _POST Endpoint URL_ with the location where you will receive the token from Content Controller.
+3. To start displaying the Lead-in page in front of any launched dispatch check _Display Lead-in page_.
+4. Choose whether a failure response sent to Content Controller will keep the course from launching.
+5. Save the settings.
+
+#### JWT Header
+Key | Value | Description
+----|---------|-------------------------------------
+typ | "JWT" |
+alg | "HS512" | The algorithm used to sign the JWT.
+
+#### JWT Payload
+Key | Value | Description
+------|-------------------------------------------------------------------------------------|---------------------------
+aud | "Lead-inPageHost" | The audience of tokens issued.
+lid | "user@example.com" (example) | The Learner ID of an individual launching a course.
+rls | "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator" (example) | The LTI User roles.
+iss | "ContentController" (default value) | The Issuer of the JWT. Can be configured through our playbooks.
+title | "Test Title" (example) | Title of the course being launched.
+exp | "1440" (default value) | Expiration for the JWT token in seconds. Can be configured through the playbooks.
+aid | "2" (example) | The Content Controller account ID associated with the dispatch package.
+an | "Test Account" (example) | The account name of the Content Controller account associated with the dispatch package.
+ait | "1660676709" (example) | The date which the token was created.
+cid | "1" (example) | The content ID of the course being launched.
+
+#### JWT Signature
+Key | Value | Description
+-------|------------------------------------------------------------------------------------|-------------------------------------
+secret | "FFPJiHlnFQxiRLUDPJYL8rAHHhrLNgqKClR60uh6P28W1C9hZoDqrTWfpCrIxyOO" (default value) | The secret key used to sign tokens. Can be configured via the playbooks.
diff --git a/additional_doc/content/features/rxd-cc.md b/additional_doc/content/features/rxd-cc.md
new file mode 100644
index 0000000..27243c1
--- /dev/null
+++ b/additional_doc/content/features/rxd-cc.md
@@ -0,0 +1,202 @@
+---
+title: "Rustici Cross Domain for Content Controller"
+type: docs
+menu:
+ main:
+ name: RXD
+ identifier: rxd
+ parent: features
+ weight: 6
+---
+
+# Rustici Cross Domain
+
+## Introduction
+If your training content is tightly integrated with your platform and not easy to package for third party use, you can use Content Controller to outfit your content with Rustici Cross Domain. Rustici Cross Domain will allow your content to remain on your servers, but harness the power of Content Controller to share that content out to your clients via proxy packages.
+
+When a learner launches the proxy package in the third party LMS, they will be directed back to your servers via links you set up in Content Controller. Once the learner exits the content, they will be directed back to the third party LMS via Content Controller. Key information is saved in Content Controller reports so that you can see who is taking your content without having to set up reporting in your environment.
+
+## Getting Started
+
+You'll start by logging into your Content Controller instance to grab both the sample file and the `contentapi.html` file. Instructions to do so are documented [here]( https://guide.contentcontroller.com/getting-started/add-a-course#importing-using-rustici-cross-domain).
+
+Looking at the source of the sample file will show you a simple example of a course that uses the RXD methods to do things like setting the status, setting a score, and other calls that may be important to use in your course. You will do similar Javascript calls in your content to communicate those values to the LMS.
+
+The `contentapi.html` file will be used to initialize the RXD API so that your course can make those calls. You will take this file and deploy it on your server so it is accessible in the same domain as where your content lives. Once you have done so, you will use the url to the file when creating the RXD course in Content Controller.
+
+Since your content will be launched inside the `contentapi.html` page, it can access the RXD API object by calling `window.parent.RXD`.
+
+~~~
+
+~~~
+
+Once you have done, so, you are free to use the following methods.
+
+## Content API
+
+### Initialize()
+
+This function is automatically called by the `contentAPI.html` page when the content is launched. You should not need to use it unless you have replaced the default landing page as described in Customizations Landing Page.
+
+### ConcedeControl()
+
+This function will pass control back to the LMS and trigger the content window to be closed.
+
+## Storing and Retrieving General Data
+
+NOTE: All values set with RXD are held locally in the JavaScript object and they are initialized on launch with values from the LMS. Getting a value using the below methods gets the locally stored value and not neccesarily the value currently on the LMS.
+
+### GetStudentID()
+
+Returns the ID of the student currently taking the content. There is no technical reason why you should need this value unless your content is performing some functionality that is outside the scope of the standards or unless the content displays the student’s ID.
+
+### GetStudentName()
+
+Returns the name of the student currently taking the content. Formatted “Last Name,First Name”
+
+### GetLessonMode()
+
+Returns the mode in which the content was launched. Possible values are:
+
+* `normal` - The content was launched intending to record full status and/or scoring data
+* `browse` - The content was launched expecting to not record status and/or scoring data
+* `review` - The content was launched having already recorded status and/or scoring data, not expecting it to be updated
+
+Calling RXD status and/or scoring functions will trigger messages to be passed to the LMS and that LMS may choose to accept or reject them based on the lesson mode that was provided during launch.
+
+### GetBookmark()
+
+This function returns the last value that was saved using `SetBookmark`. If `SetBookmark` was not called previously, this function returns an empty string. This function is useful to call at the beginning of the content to retrieve the user’s last location and give him/her the option to return to that location.
+
+### SetBookmark(val)
+
+Saves a string that represents the user’s current location in the content. This string can be formatted however you choose. This function should be called periodically as the user progresses through the content. Note for SCORM 1.2 based proxy packages the string has a limit of 255 characters. LMSs may restrict the size for other standards as well.
+
+### GetSuspendData()
+
+This function returns the last value that was saved to the suspend data bucket. If `SetSuspendData` was not called previously, or there was no suspend data available on launch, this function returns an empty string.
+
+### SetSuspendData(val)
+
+This function allows you to save a string of data. This data can represent anything; it is just an arbitrary chunk of data. This function is useful for saving internal state data that does not correspond to any other data element. Note for SCORM 1.2 based proxy packages the string has a limit of 4000 characters. LMSs may restrict the size for other standards as well.
+
+## Status Functions
+
+### GetStatus()
+
+Returns the current lesson status of the content. Possible values are:
+
+* `passed` - The user completed the content with a score sufficient to pass.
+* `failed` - The user completed the content but his/her score was not sufficient to pass.
+* `completed` - The user completed the content.
+* `incomplete` - The user began the content but did not complete it.
+* `browsed` - The user looked at the content but was not making a recorded attempt at it.
+* `not_attempted` - The user has not started the content.
+* `unknown` - The content received an unrecognized status, or the status was reset via `ResetStatus`
+
+### SetReachedEnd()
+
+You can optionally call this function to indicate that the user has made sufficient progress in the content to be considered complete. This function is useful for content that has a final diploma or confirmation page. When that final page is reached, you should call `SetReachedEnd`. After calling this function a call to `GetStatus` will return `completed` *unless* the content was previously marked as `passed` or `failed`.
+
+### SetPassed()
+
+Sets the current status of the content to passed. If there is a mastery score stored in the LMS (as specified in your content packaging) and if you record a score for the content, then this function does not need to be called; the LMS will automatically compare the score to the mastery score to determine if the content was passed or failed.
+
+### SetFailed()
+
+Sets the current status of the content to failed. If there is a mastery score stored in the LMS (as specified in your content packaging) and if you record a score for the content, then this function does not need to be called; the LMS will automatically compare the score to the mastery score to determine if the content was passed or failed.
+
+### ResetStatus()
+
+Resets the status of the content back to incomplete if you previously set it to passed or failed. A call to `GetStatus` will return `unknown` after calling this function.
+
+## Assessments
+
+### GetScore()
+
+Retrieves the raw score previously recorded by `SetScore` in this session. If no score was previously set (since launch), this function returns `undefined`.
+
+### SetScore(score, max, min)
+
+Allows you to record the score that the user achieved in the content. You should specify the minimum and maximum scores that were available. All scores should be numbers (float) between 0 and 100.
+
+### Interactions
+
+The following set of functions record interaction data and all take the same set of arguments.
+
+* `RecordTrueFalseInteraction`
+* `RecordMultipleChoiceInteraction`
+* `RecordFillInInteraction`
+* `RecordMatchingInteraction`
+* `RecordPerformanceInteraction`
+* `RecordSequencingInteraction`
+* `RecordLikertInteraction`
+* `RecordNumericInteraction`
+
+The function arguments are as follows:
+
+* `id` - string identifier for the interaction,
+* `learnerResponse` - learner's response to the interaction, format depends on the type of interaction,
+* `isCorrect` - boolean indicating whether the learner's response was correct,
+* `correctResponse` (optional) - expected correct response, format depends on the type of interaction,
+* `description` (optional) - string description of the interaction (i.e. the question asked),
+* `weighting` (optional) - a number that indicates the importance of the interaction relative to other interactions,
+* `latency` (optional) - number of milliseconds passed during interaction,
+* `learningObjectiveId` (optional) - string identifier of a learning objective associated with this interaction
+
+An example call to record an interaction might look like the following:
+
+ RXD.RecordMultipleChoiceInteraction("alpha-mc-1", "a", true, "a", "Which letter is first in the alphabet?", 1, 750, "alphabet1");
+
+#### Response Formats
+
+Multiple choice and seqeuencing interactions can be provided as simple strings, or via an object that provides a short and a long representation. A short representation will always be generated if the recording LMS does not support long identifiers using the first character of the long representation if both are not provided. For SCORM 2004 based packages the identifier will be transformed into a URI if it is not provided as one using the underlying Rustici Driver code which will prefix the provided response with `urn:scormdriver:`. To provide the specific response identifier an object notation similar to the following should be used:
+
+ {
+ short: "a",
+ long: "alpha"
+ }
+
+For both types of interactions an array of objects similar to the above can also be provided:
+
+ {
+ short: "a",
+ long: "alpha"
+ },
+ {
+ short: "b",
+ long: "beta"
+ }
+
+Matching interactions expect a response to include a source value and a target value, and may include multiple responses. For example a single matching response might take the form:
+
+ {
+ source: "left",
+ target: "west"
+ }
+
+And multiple match responses could be recorded using an array of objects like the above:
+
+ [
+ {
+ source: "top",
+ target: "north"
+ },
+ {
+ source: "right",
+ target: "east"
+ },
+ {
+ source: "bottom",
+ target: "south"
+ },
+ {
+ source: "left",
+ target: "west"
+ },
+ ]
+
+
diff --git a/additional_doc/content/features/saml.md b/additional_doc/content/features/saml.md
new file mode 100644
index 0000000..3096559
--- /dev/null
+++ b/additional_doc/content/features/saml.md
@@ -0,0 +1,134 @@
+---
+title: "SAML"
+type: docs
+menu:
+ main:
+ name: SAML Integration
+ identifier: saml
+ parent: features
+ weight: 5
+---
+
+# SAML SSO
+
+## Introduction
+SAML allows an organization to create a single source of truth for user identity management and communicate this
+information with other applications. The user identity source is referred to as the Identity Provider (IdP) which
+communicates with Service Providers (SP). In this context, Content Controller is the Service Provider and an application
+like Active Directory would be the Identity Provider.
+
+The following steps will describe how to integrate your Identity Provider with Content Controller.
+
+## Setup
+Before starting the configuration of SAML on the Content Controller side of things, first you must obtain a metadata XML
+file from your identity provider and place it in the `roles/saml/files` directory of the playbooks and name it
+`idp-metadata.xml`.
+
+Next, if you do not already have one, you will need to generate a keystore for all signature and encryption operations.
+Execute the following commands, replacing `{private_key_password}` and `{keystore_password}` with strong passwords.
+Keep track of these two passwords as they will be added to the Content Controller configuration later.
+
+```
+keytool -genkeypair -alias pac4j-demo \
+ -keystore samlKeystore.jks -keyalg RSA -keysize 2048 -validity 3650 \
+ -keypass {private_key_password} -storepass {keystore_password}
+```
+
+This command will output a file named `samlKeystore.jks`. Where you run the command isn't important, as long as the
+`samlKeystore.jks` is copied onto the Ansible control server. It needs to be moved into the `roles/saml/files` directory
+of the playbooks.
+
+## Content Controller Configuration
+With these files now in the proper place, open your `group_vars/content_controller.yml` configuration file in the
+Ansible playbooks. Find the placeholder values for these settings and update them to the following:
+
+```
+enable_saml: true
+private_key_password: {private_key_password used to generate keystore}
+keystore_password: {keystore_password used to generate keystore}
+maximum_auth_lifetime: 3600
+include_query_param_on_callback: true
+```
+
+`maximum_auth_lifetime` is the maximum age in seconds that Content Controller will allow for a user. If a token older
+than this lifetime is used in a request, then CC will reject it. The value configured in CC should be shorter than or
+equal to the same value in your IdP server.
+
+`include_query_param_on_callback` determines whether or not query parameters are included on the SAML callback URLs.
+This is configurable as some Identify Providers do not accept query parameters. If Content Controller doesn't include
+query parameters, the 'client_name' property will be set as part of the SAML resolver.
+
+### Identifying Users
+
+After your IdP authenticates a user, it will send information about that user to Content Controller in the form of
+attributes on a profile. By default, CC will use the value of the `email` attribute as the user ID when a user logs in
+through SSO. However, if there is an attribute on the SAML profile that you would prefer as the identifier, you can use
+the setting `saml_identifying_attribute`. The value of this setting should be the name of the attribute that should be
+used to uniquely identify users. The value of the attribute will be used to uniquely identify users coming from your
+IdP, so please ensure that the value of the attribute is unique for each user.
+
+### Authorizing Users
+
+By default, Content Controller will allow any user that comes from your IdP. However, CC can be configured to authorize
+users based on the attributes that are sent back your IdP. If you would like to limit the users that can access the
+application, then you can use the following settings:
+
+* `saml_access_attribute`: The name of the attribute used to authorize users.
+* `saml_access_value`: The value required to be in or equal to the `saml_access_attribute`.
+* `saml_access_condition`: The operation used to verify that the `saml_access_value` is present in the
+ `saml_access_attribute`. Can be `equals`, `starts_with`, or `contains`. If not specified, will default to `contains`.
+
+If the above settings are configured, Content Controller will first verify that the `saml_access_attribute` is present
+on the profile sent by the IdP. Then, depending on the configured `saml_access_condition`, CC will verify that the
+attribute value either equals, starts with, or contains the `saml_access_value`.
+
+For example, if you wanted to require that users SSOing into Content Controller have an attribute `teams` that contains
+the string `content_controller`, then you would set the following:
+
+```
+saml_access_attribute: "teams"
+saml_access_value: "content_controller"
+```
+
+If you wanted to be very strict and require that users have the attribute `cc_admin` set to `true`, then you would set
+the following:
+
+```
+saml_access_attribute: "cc_admin"
+saml_access_value: "true"
+saml_access_condition: "equals"
+```
+
+## IdP Configuration
+
+To integrate Content Controller with your IdP server, you will need to provide a service provider metadata file. This
+file is used to configure your IdP so that it knows how to communicate with Content Controller. Because these steps
+rely on saving a file to disk, if you've multiple app servers behind a load balancer, then this process will be more
+reliable if you can execute the requests directly against one app server.
+
+After you've deployed Content Controller, first visit the endpoint `[domain or IP]/api/saml`. This will attempt to
+initiate an SSO login, and probably redirect you over to the IdP. This process will generate a SAML metadata file on
+disk. If you get an error back from that endpoint, then there may be an issue with your SAML configuration. Refer to the
+application logs for more details.
+
+Once you've generated the service provider metadata, you can access it by hitting the endpoint
+`[domain or IP]/api/saml/metadata`. It will return an XML document that you can save to the file system. Then, provide
+it to your IdP in the appropriate manner. This will vary depending on which IdP you are using; for more information,
+refer to that product's documentation or support team.
+
+Content Controller will need permission to initiate the SSO process. Some IdP's may require special configuration to
+enable SP-Initiated SSO. If the specific binding is required, please add
+`urn:oasis:names:tc:SAML:profiles:SSO:request-init` as one of the allowable SAML bindings.
+
+By default, Content Controller requires an `email` attribute on the profile your IdP provides. If that is not included
+by default, then you may need to do a bit of extra configuration to ensure that information is communicated to the
+application. Alternatively, you can refer to the "Identifying Users" section above for how to configure Content
+Controller to not require an `email` attribute.
+
+
+## Use
+
+Once enabled, a new `SSO` button will appear on the login page that will redirect the user to your Identity Provider
+login page. If authentication with the IdP is successful, then the user will be redirected back to Content Controller
+and issued a valid authentication token. If a CC profile already exists with that email address, then the user will be
+logged in using that profile. If such a user does not already exist in the application, then one will be created.
diff --git a/additional_doc/content/features/webhooks.md b/additional_doc/content/features/webhooks.md
new file mode 100644
index 0000000..6d13879
--- /dev/null
+++ b/additional_doc/content/features/webhooks.md
@@ -0,0 +1,581 @@
+---
+title: "Webhooks"
+type: docs
+menu:
+ main:
+ name: Webhooks
+ identifier: webhooks
+ parent: features
+ weight: 5
+---
+
+
+# Webhooks
+
+
+## Introduction
+Content Controller 4.0 introduced support for webhooks, which enables Content Controller to send notifications to a target system when certain events take place. Webhooks can be triggered by events that take place in the UI or through the Automation API, and the delivered messages will describe the event that happened. Where Content Controller will send these messages is referred to as the 'target' system and is configurable for each webhook.
+
+The following sections will describe how to configure and enable Content Controller's Webhooks feature:
+
+* [Setup](#setup): Describes the steps required to setup and configure the required message queues. This section is more important for those who are self-hosting Content Controller.
+* [Content Controller Config](#config): Describes the Content Controller configuration settings that impact webhooks behavior. This section is more important for those who are self-hosting Content Controller.
+* [Webhook Configuration](#webhookconfig): Describes the different ways webhooks can be configured
+* [Postback Messages](#messages): Describes the postback messages Webhooks will deliver
+* [Webhook Statistics](#statistics): Describes how to track a webhook's usage
+* [How to Use](#use)
+
+{{< line_break >}}
+{{< line_break >}}
+
+## Setup {#setup}
+This section is more important for those who are self-hosting Content Controller. Content Controller incorporates some AWS products to support the scaling and ordering of the webhook's postback messages. At this time, you must have a valid AWS configuration to take advantage of the Webhooks feature. See the [AWS](/self-hosting/aws/aws) for more information.
+
+Specifically, Content Controller relies on the AWS SQS service to handle the ordering and scaling of webhook messages. Content Controller relies on FIFO (first in, first out) queues to maintain the order of webhook message events. In order to enable the webhooks feature, Content Controller will need two SQS FIFO queues: (1) a message queue and (2) a dead-letter queue.
+
+From Content Controller's perspective, the only requirement for the SQS queue is that the queue is of FIFO type. This selection is at the very top of the 'Create Queue' details section on AWS Console. It is strongly recommended that you enable the 'Dead Letter Queue' on the message queue to point to the dead-letter queue. NOTE: Content Controller's true number of attempts will be the smaller value between the SQS queue's redrive policy 'Maximum Receives' count and the specific webhook's `max_attempts` configuration. See [Retrying](#retrying) for more information.
+
+One important thing to understand is that Content Controller should be the only application that is polling these queues for messages. If another application polls the queue and processes or deletes messages, Content Controller will be unable to send the webhook notification that corresponds with that message, which will result in lost webhook notifications.
+
+With these two queues created, Content Controller will need to know the names of the queues in its configuration.
+
+{{< line_break >}}
+
+## Content Controller Configuration {#config}
+This section is more important for those who are self-hosting Content Controller. After the creation of the message and dead-letter queues, open your `group_vars/content_controller.yml` configuration file in the Ansible playbooks. Find the placeholder values for these settings and update them to the following:
+
+```
+enable_webhooks_service: true
+enable_queue_polling: false
+message_queue_name: "{q_name.fifo}"
+dead_letter_queue_name: "{dlq_name.fifo}"
+queue_polling_interval: 10
+webhooks_auth_secret: "{secret}"
+```
+
+
+`enable_webhooks_service` - This setting tells Content Controller to enable the triggering of webhook events. When disabled, Content Controller will not trigger any new webhook events, nor will it poll the SQS queue for any messages that are ready for delivery to the target system. When disabled, existing webhooks enter a 'Read Only' state, and can not be updated or deleted.
+
+
+`enable_queue_polling` - This setting determines whether or not Content Controller will poll messages off the message queue. When this setting is disabled, Content Controller will still build postback messages when events that trigger webhook notifications happen. The messages will be sent to the queue, but Content Controller will ultimately not do anything with the messages once that happens. This setting can be used if you have another system you want to poll the message queue.
+
+
+`queue_polling_interval` - Tells Content Controller how frequently, in seconds, it should poll the SQS queue for available messages. Content Controller utilizes long polling, which reduces the number of empty polls and returns messages as soon as they are available. The default interval value is 10 seconds.
+
+
+`webhooks_auth_secret` - This is a 64-character string that is used in the process of encrypting and storing the webhook postback key and secret values in the database.
+
+{{< line_break >}}
+
+## Webhook Configuration {#webhookconfig}
+When creating or updating a webhook, you must define certain properties that identify and direct the webhook's behavior. For these requests, you must define a webhook's `name`, `topic`, and `target_url`. There are many other properties that are optional. Here is an example schema that could be used to Create or Update a webhook:
+```
+{
+ "name": "new webhook configuration",
+ "topic": "ACCOUNT",
+ "subtopics": [
+ "ACCOUNT_DELETED"
+ ],
+ "focus": [
+ {
+ "webhook_focus_type": "ACCOUNT",
+ "webhook_focus_id": 23,
+ "webhook_focus_name": "Special Account"
+ }
+ ],
+ "target_url": "https://target.system.com",
+ "authentication": {
+ "type": "BASIC",
+ "key": "demoKey",
+ "secret": "demoSecret"
+ },
+ "enabled": true
+}
+```
+The above schema would create an active webhook named "new webhook configuration" that would trigger when the "Special Account" is deleted and would send a message to **`https://target.system.com`** with an Authorization header (with a basic auth string created with 'demoKey' and 'demoSecret').
+
+
+Webhooks are not allowed to have an explicitly empty “subtopic” list. This is enforced: in the UI, by making a valid Subtopic selection a requirement when creating or updating a webhook, and in the Automation API, by rejecting requests that have an empty (`subtopics: []`) “subtopic” definition. NOTE: Automation API requests that have no explicitly defined “subtopic” definition will be interpreted as the full list of Subtopics.
+
+
+For example, a `POST /webhooks` request with the following request schema would trigger notifications when an account is created, has its activation status updated, and deleted:
+```
+{
+ "name": "Account Webhook with an explicitly empty Subtopics list",
+ "target_url": "https://target.system.com",
+ "topic": "ACCOUNT"
+}
+```
+
+
+However, the following request is invalid:
+```
+{
+ "name": "Account Webhook with an explicitly empty Subtopics list",
+ "target_url": "https://target.system.com",
+ "topic": "ACCOUNT",
+ "subtopics": []
+}
+```
+
+
+### Properties Accessible via API
+There are a few webhook settings that are not presented in the UI and are only accessible via the Automation API. These include:
+
+
+`max_attempts` - integer between 1 and 1000 that indicates the total number of times a webhook will try sending a postback message should the initial attempt fail.
+
+
+`ignore_before_dt` - webhooks can be configured to ignore messages from events that were triggered before a specific point in time (ISO datetime). This property can be used should the message queue get significantly backed up via a configuration issue.
+
+
+`logging_mode` - defines the level of detail the webhook should record in log statements. See the [Logging](#logging) section below for details.
+
+
+### Supported Webhook Triggers
+Webhook messages can be triggered by a variety of events. The broadest categories of those, referred to as Topics, include `ACCOUNT`, `ACCOUNT_CONTENT`, `COURSE`, and `REGISTRATION`. A Topic is required to be defined for every webhook.
+
+
+We also support Subtopics, which are more specific actions within a Topic. If defined, the webhook will trigger only for those Subtopics.
+
+
+Here are the sets of topics with their associated Subtopics that can trigger webhook notifications in Content Controller:
+
+* ACCOUNT
+ * **Account created\***: A new account has been created in Content Controller.
+ * **Account activation updated**: An account's activation status has been updated (the webhook notification message will indicate if the account is now 'active' or 'inactive').
+ * **Account deleted**: An account has been deleted from Content Controller.
+
+* ACCOUNT_CONTENT
+ - **Content added to account**: A course, equivalent, or bundle has been added to an account.
+ - **Content removed from account**: A course, equivalent, or bundle has been removed from an account.
+
+* COURSE
+ - **Course Imported\***: A new course has been imported to Content Controller's Global Content Pool.
+ - **Course Version Uploaded**: A new version of a course has been uploaded to Content Controller.
+ - **Course Version Published**: A course version has been published in the Global Content Pool.
+
+* REGISTRATION
+ - **Registration launched**: A new registration has been launched.
+ - **Registration status updated**: The Completion, Success, or Score values for a registration have been updated.
+
+
+\* availabilty of these Subtopics are impacted by the webhook's defined Focus. See the [Webhook Focus](#focus) section below for more details.
+
+
+The webhook postback messages will describe the event that happened, as well as note any related assets. For details about what information is included in the postback messages for each event, see the [Message Schemas](#schemas) section below.
+
+
+
+### Webhook Focus {#focus}
+A webhook with an 'ACCOUNT_CONTENT' Topic would trigger when any piece of content is added or removed from any account. That may be intentional, but it would be very noisy for larger systems.
+
+
+Instead, it may be a good idea to pick a specific course or account to be the Focus of the webhook, and the postback messages will be filtered to only the events taken against those Content Controller assets.
+
+
+The following Focus options are available for each topic:
+
+* `ACCOUNT` webhooks can be focused on specific accounts.
+* `ACCOUNT_CONTENT` webhooks can be focused on specific accounts or specific content.
+* `COURSE` webhooks can be focused on specific courses.
+* `REGISTRATION` webhooks can be focused on specific accounts or specific content.
+
+
+A webhook can focus on more than one type of asset, but then it must have both Focuses satisfied before a notification will be sent.
+
+
+There is a Subtopic limitation when defining a webhook's Focus. Certain Subtopics don't apply when the webhook is focused on a specific asset. For example, a webhook that is focused on a specific account would never trigger for the 'Account Created' event. Similarly, a webhook focused on specific courses would never trigger for the 'Course Imported' event. Content Controller rejects attempts to configure webhooks with invalid Subtopic/Focus combinations.
+
+
+
+### Logging {#logging}
+The Content Controller logs can provide good insight when webhooks behave in an unexpected manner. Each webhook can be configured to change what gets logged at the INFO level which is the default level for Content Controller.
+
+
+There are four available logging modes for each webhook:
+* `NONE`: No log statements will be generated for events that impact this webhook.
+* `SUMMARY`: Generate a minimal log that specifies the webhook event that occurred, the ID of the impacted webhook, and whether the event was successful or not.
+* `FULL`: Same as `SUMMARY`, but also include the full webhook message body, and any response schemas, if applicable.
+* `FULLONERROR`: treated as `SUMMARY` if the webhook event was successful and as `FULL` if webhook processing encountered an error.
+
+By default, webhooks have the `FULLONERROR` logging mode.
+
+If other configurations that impact logging are set for DEBUG logging, the webhook's logging level can be overridden. For example, if the class that triggers the webhook event is set to DEBUG logging (via the `cc_custom_log_levels` configuration), the impacted webhooks will log at the FULL level. Similarly, if Content Controller itself is set to DEBUG logging, all webhooks will generate FULL logs. However, a less-verbose logging level defined for the class or for Content Controller will not override a webhook's more-verbose logging level.
+
+
+### Retrying {#retrying}
+Should Content Controller encounter an issue in delivering the webhook notification message to the target system, it will attempt to retry the message processing. Each webhook has a `max_attempts` property that determines how many times Content Controller will attempt to send messages for that webhook. After attempting and failing to deliver the message `max_attempts` number of times, Content Controller will deliver the message to the configured Dead Letter Queue. Content Controller will make sure to update the webhook's statistics and create log messages that should describe the issue it encountered.
+
+
+In addition to the webhook-specific configuration, the SQS message queue itself can specify how many times a message should be retried before being delivered to the Dead Letter Queue. This is known as the queue's Redrive Policy. NOTE: If the value of the SQS queue’s Redrive Policy is less, it can override the webhook's `max_attempts` setting. For example, if the webhook has a `max_attempts` value of 10, but the queue’s Redrive Policy is configured to attempt delivering messages 3 times, Content Controller will direct the message to the DLQ after 3 failed attempts. Content Controller's true number of attempts will be the smaller value between the SQS queue's Redrive Policy 'Maximum Receives' count and the specific webhook's `max_attempts` configuration.
+
+{{< line_break >}}
+
+## Postback Messages {#messages}
+Webhooks are used to deliver notification messages to outside systems that can then act upon the received messages. When Content Controller triggers a webhook event, it will build up a message that describes the event and send a POST request to the target system at the `target_url` configured for the webhook. The request will include the webhook notification message as a JSON body on the request.
+
+
+### Target System Authentication
+At this time, Content Controller supports Basic Auth as an option for adding authorization to the webhook postback messages. Content Controller will build and include an Authorization header with the postback message, which the target system can then use to verify access and either allow or reject messages. Webhooks that are configured, either through the UI or API, with a 'Key' and 'Secret' will include that Authorization header with the postback message.
+
+
+By default, webooks are configured with "NONE" authentication type. With this configuration, Content Controller will not include an Authorization header with the webhook postback message.
+
+
+### Message Schemas {#schemas}
+The following are examples of the webhook notification message payloads for each supported event.
+
+Supported Webhook triggers:
+
+* [Account Created](#account_created)
+* [Account Activation Updated](#account_activation_updated)
+* [Account Deleted](#account_deleted)
+{{< line_break >}}
+{{< line_break >}}
+* [Content Added to Account](#content_added_to_account)
+* [Content Removed from Account](#content_removed_from_account)
+{{< line_break >}}
+{{< line_break >}}
+* [Course Imported](#course_imported)
+* [Course Version Uploaded](#course_version_uploaded)
+* [Course Version Published](#course_version_published)
+{{< line_break >}}
+{{< line_break >}}
+* [Registration Launched](#registration_launched)
+* [Registration Status Updated](#registration_status_updated)
+
+{{< line_break >}}
+
+#### Account Created {#account_created}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "ACCOUNT",
+ "subtopic": "ACCOUNT_CREATED",
+ "resource": {
+ "account": {
+ "id": 15073,
+ "name": "demo-account-name",
+ "enabled": true
+ }
+ },
+ "user": "john.learner@organization.com",
+ "timestamp": "2023-10-19T13:47:57.89698Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Account Activation Updated {#account_activation_updated}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "ACCOUNT",
+ "subtopic": "ACCOUNT_ACTIVATION_UPDATED",
+ "resource": {
+ "account": {
+ "id": 15073,
+ "name": "demo-account-name",
+ "enabled": false
+ }
+ },
+ "user": "john.learner@organization.com",
+ "timestamp": "2023-10-19T13:48:01.325298Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Account Deleted {#account_deleted}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "ACCOUNT",
+ "subtopic": "ACCOUNT_DELETED",
+ "resource": {
+ "account": {
+ "id": 15073,
+ "name": "demo-account-name",
+ "enabled": false
+ }
+ },
+ "user": "john.learner@organization.com",
+ "timestamp": "2023-10-19T13:49:12.407246Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Content Added to Account {#content_added_to_account}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "ACCOUNT_CONTENT",
+ "subtopic": "CONTENT_ADDED_TO_ACCOUNT",
+ "resource": {
+ "account": {
+ "id": 15067,
+ "name": "demo-account-name",
+ "enabled": true
+ },
+ "content": {
+ "folder": {
+ "id": 1506,
+ "name": "folder-with-equivalents",
+ "location": "Home",
+ "contents": [
+ {
+ "type": "EQUIVALENT",
+ "id": 6501,
+ "name": "demo-equiv"
+ },
+ {
+ "type": "EQUIVALENT",
+ "id": 6502,
+ "name": "demo-scorm12-equiv"
+ },
+ {
+ "type": "EQUIVALENT",
+ "id": 6503,
+ "name": "demo-scorm2004-3-equiv"
+ },
+ {
+ "type": "EQUIVALENT",
+ "id": 6504,
+ "name": "demo-scorm2004-4-equiv"
+ }
+ ]
+ }
+ }
+ },
+ "user": "john.learner@organization.com",
+ "timestamp": "2023-10-19T13:51:17.030713Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Content Removed from Account {#content_removed_from_account}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "ACCOUNT_CONTENT",
+ "subtopic": "CONTENT_REMOVED_FROM_ACCOUNT",
+ "resource": {
+ "account": {
+ "id": 15067,
+ "name": "demo-account-name",
+ "enabled": true
+ },
+ "content": {
+ "bundle": {
+ "id": 3952,
+ "name": "demo-content-with-interactions",
+ "location": "Home"
+ }
+ }
+ },
+ "user": "john.user@organization.com",
+ "timestamp": "2023-10-19T13:51:37.106559Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Course Imported {#course_imported}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "COURSE",
+ "subtopic": "COURSE_IMPORTED",
+ "resource": {
+ "content": {
+ "course": {
+ "id": 31230,
+ "name": "Golf Explained - Run-time Advanced Calls",
+ "version_id": 0,
+ "learning_standard": "SCORM_2004_3RD_EDITION",
+ "location": "Home > demo_content > nested_folder"
+ }
+ }
+ },
+ "user": "john.user@organization.com",
+ "timestamp": "2023-10-19T13:53:33.74762Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Course Version Uploaded {#course_version_uploaded}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "COURSE",
+ "subtopic": "COURSE_VERSION_UPLOADED",
+ "resource": {
+ "content": {
+ "course": {
+ "id": 31230,
+ "name": "Golf Explained - Run-time Advanced Calls",
+ "version_id": 1,
+ "learning_standard": "SCORM_2004_3RD_EDITION",
+ "location": "Home > demo_content > nested_folder"
+ }
+ }
+ },
+ "user": "john.user@organization.com",
+ "timestamp": "2023-10-19T13:53:52.625231Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Course Version Published {#course_version_published}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "COURSE",
+ "subtopic": "COURSE_VERSION_PUBLISHED",
+ "resource": {
+ "content": {
+ "course": {
+ "id": 31230,
+ "name": "Golf Explained - Run-time Advanced Calls",
+ "version_id": 1,
+ "learning_standard": "SCORM_2004_3RD_EDITION",
+ "location": "Home > demo_content > nested_folder"
+ }
+ }
+ },
+ "user": "john.user@organization.com",
+ "timestamp": "2023-10-19T13:54:00.284256Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Registration Launched {#registration_launched}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "REGISTRATION",
+ "subtopic": "REGISTRATION_LAUNCHED",
+ "resource": {
+ "account": {
+ "id": 15023,
+ "name": "demo-account-name",
+ "enabled": true
+ },
+ "content": {
+ "course": {
+ "id": 31099,
+ "name": "demo-aicc-course-name",
+ "version_id": 0,
+ "learning_standard": "AICC",
+ "location": "Home > demo-aiccFolder > nested-aiccFolder"
+ }
+ },
+ "registration": {
+ "account_id": 15023,
+ "registration_id": "28690",
+ "course_id": 31099,
+ "learner_id": "john.learner@organization.com"
+ }
+ },
+ "user": "john.learner@organization.com",
+ "timestamp": "2023-10-19T13:57:37.357176Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+#### Registration Status Updated {#registration_status_updated}
+```
+{
+ "subscription_id": 2,
+ "name": "demo-webhook",
+ "topic": "REGISTRATION",
+ "subtopic": "REGISTRATION_STATUS_UPDATED",
+ "resource": {
+ "account": {
+ "id": 15023,
+ "name": "demo-account-name",
+ "enabled": true
+ },
+ "content": {
+ "course": {
+ "id": 31099,
+ "name": "demo-aicc-course-name",
+ "version_id": 0,
+ "learning_standard": "AICC",
+ "location": "Home > demo-aiccFolder > nested-aiccFolder"
+ }
+ },
+ "registration": {
+ "account_id": 15023,
+ "registration_id": "28690",
+ "course_id": 31099,
+ "learner_id": "john.learner@organization.com",
+ "completion": "Completed",
+ "success": "PASS",
+ "score": 80,
+ "duration": 24,
+ "completion_dt": "2023-10-19T13:58:05.000Z"
+ }
+ },
+ "user": "john.learner@organization.com",
+ "timestamp": "2023-10-19T13:58:04.737692Z",
+ "target_url": "https://target.system.com",
+ "logging_mode": "FULL_ON_ERROR",
+ "max_attempts": 1
+}
+```
+
+{{< line_break >}}
+
+## Webhook Statistics {#statistics}
+Content Controller keeps a record of a webhook's success and failure count, as well as the webhook's last encountered error message. From the Content Controller UI, users would be able to tell if a webhook is 'in error' (the webhook has encountered a failure more recently than a success) by the Alert symbol beside the webhook name on the Webhooks List page. The webhook's last error message would also appear in the Webhook Detail panel. These alerts should clear after that webhook successfully delivers a postback message or after the webhook is edited.
+
+
+When using the Automation API, you get access to a few more details. Here's an example payload from the `GET /webhooks/{webhook_id}/statistics` endpoint:
+```
+{
+ "statistics_valid_from_dt": "2024-01-01T13:00:00.000Z",
+ "success_count": 20,
+ "last_success_dt": "2024-03-01T15:00:00.000Z",
+ "error_count": 3,
+ "last_error_dt": "2024-03-05T14:00:00.000Z",
+ "last_error_message": "Request to postback URL 'https://target.system.com' failed for reason: Forbidden "
+}
+```
+
+Through the API, you can view the success and failure counts for a webhooks, as well as the error message from the last failure. In the example payload above, the `last_error_dt` is more recent than the `last_success_dt`, so this webhook would be considered 'in error' and would display the alert icons in the UI.
+
+Finally, a webhook's statistics can be reset via the API. Using the `POST /webhooks/{webhook_id}/statistics/reset` endpoint or `PUT /webhooks/{webhook_id}` with `resetStatistics=true` as a query parameter, you can clear out the success and failure records for a webhook. Doing so will update the 'valid_from_dt' value to the current timestamp.
+
+{{< line_break >}}
+
+## How to Use {#use}
+In the UI, Webhooks can be accessed by Admin users via the Administration page. Navigate to the Integrations tab and select 'Webhooks' from the sidebar. This is the Webhooks list page, which will display all webhooks as well as some basic information about each one. A link to the User Guide is [here](https://guide.contentcontroller.com/settings/webhooks).
+
+Webhooks can be accessed via the Automation API via a set of `/webhooks` endpoints. For more details, see the documentation for our Automation API [here](/automation-api/v1).
+
diff --git a/additional_doc/content/security.md b/additional_doc/content/security.md
new file mode 100644
index 0000000..7b59404
--- /dev/null
+++ b/additional_doc/content/security.md
@@ -0,0 +1,143 @@
+---
+title: "Security"
+type: docs
+menu:
+ main:
+ name: Customizing Security Settings
+ identifier: security
+ weight: 2
+---
+
+# Customizing Security Settings
+
+By default, Content Controller ships with security settings that work for most people. However, sometimes tighter
+security is more desireable than supporting older browsers. Most security-related settings can be adjusted by changing
+a few playbook variables.
+
+## HTTP Strict Transport Security (HSTS)
+
+If you need to support LMSs that only work with HTTP, then you can not use this setting. If you want to force HTTPS
+access semi-permanently, you can enable
+[HTTP Strict Transport Security](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security). **Only do this if
+you're sure that you do not need to support HTTP-only LMSs.** Once the setting is enabled, browsers will be locked to
+loading over HTTPS only for at least 6 months.
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/env.yml`.
+2. If you're terminating SSL at Apache, edit the line that starts with `allow_80` and set it to `allow_80: false`;
+ if you're terminating SSL at your load balancer (and/or using CloudFront), set it to `allow_80: true`.
+3. Add this line under `# SSL Configuration`: `use_hsts: true`
+4. If you would like to adjust the time of the HTTPS lock, add the line setting the `hsts_max_age` with the desired
+ value in seconds.
+5. If you'd like to include the `includeSubDomains` option in the HSTS header, then also add the line
+ `hsts_include_subdomains: true`. [RFC 6797](https://tools.ietf.org/html/rfc6797#section-14.4) recommends this option,
+ but it should not be used if any subdomain of your Content Controller installation could possibly need to allow
+ HTTP-only access in the future.
+6. Save and exit.
+7. Run the playbooks to deploy your changes.
+
+## Session Timeout
+
+By default, session timeout is 24 hours. The minimum allowed value is 1 minute, and the maximum allowed value is 43200
+minutes (30 days).
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/content_controller.yml`.
+2. Add this line `token_exp: 1440`, but replace `1440` with your desired session length in minutes.
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+## Public API Auth Token Expiration
+
+By default, the public API auth token expires after 30 minutes.
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `roles/content-controller/defaults/main.yml`.
+2. Add this line `public_api_auth_expiration: #`, but replace `#` with your desired session length in minutes.
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+## Disabling Inactive User Accounts
+
+Users that have experienced a specific number of days since its last login and last unlock are considered inactive. By
+default, this behavior is disabled.
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/content_controller.yml`.
+2. Add this line `user_account_days_inactive_threshold: 30`, and replace `30` with your desired inactivity threshold in
+ number of days.
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+## SSL Cipher Suites
+
+Ciphers can be enabled or disabled by supplying an SSL cipher suite config. By default, we use [this config provided by
+Mozilla's SSL Configuration Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.4.28&openssl=1.0.1f&hsts=no&profile=intermediate).
+If you don't need to support older browsers, you can use the **SSLCipherSuite** provided by choosing the **Modern**
+settings.
+
+## Using SSL for Database Traffic
+
+Content Controller can be configured to encrypt traffic to the database using SSL/TLS. At a minimum, you just need to
+add `db_use_ssl: true` to one of your Ansible config files. By default, Content Controller will trust the certificates
+used by RDS databases.
+
+If you want to communicate with a database that is not in RDS, then you will also need to provide the certificate of
+that database in the `roles/ssl/files` directory of your playbooks. Then, configure the setting
+`db_ssl_cert: [certificate name]`, but replace `[certificate name]` with the name of the file you added to the
+playbooks.
+
+Additionally, there is an optional setting you can set to control the SSL protocols supported. By default,
+`db_ssl_protocols` will have the value of `"TLSv1,TLSv1.1,TLSv1.2"`. If you want to add or remove protocols from this
+default list, then override it by providing your own list of values.
+
+### Terminating SSL at CloudFront
+
+See this AWS documentation on [Supported Protocols and Ciphers](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html),
+and then update your CloudFront Security Policies and Elastic Load Balancer Security Policies to match. See the
+[CloudFront page](/self-hosting/aws/cloudfront) for a refresher on setting distribution details, and see the
+[Load Balancer page](/self-hosting/aws/load-balancer) for a refresher on setting the application load balancer config.
+
+### Terminating SSL at the Application Server
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/env.yml`.
+2. Add this line under `# SSL Configuration`, but replace `...` with your desired cipher suite config:
+ `ssl_cipher_suite: "..."`
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+## SSL Protocol
+
+By default, we allow TLS 1.0, TLS 1.1, and TLS 1.2. If you do not need to support learners using older
+browsers/operating systems (such as IE 9 and Windows Vista), then you should turn off TLS 1.0.
+
+### SSL Terminated at CloudFront
+
+Check your CloudFront Origin Behaviors and verify that you have set the desired protocols. See the
+[CloudFront](/self-hosting/aws/cloudfront) docs for a refresher on setting origin behavior details.
+
+### SSL Terminated at the Application Server
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/env.yml`.
+2. Add this line under `# SSL Configuration`: `ssl_protocol: "all -SSLv2 -SSLv3 -TLSv1"`
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+### Configuring Password Reset Limits
+
+Password reset emails can be sent through the 'Forgot Password?' button on the login page. This page does not require
+authentication to access, therefore we want to protect users against a malicious actor using this functionality to spam
+them with emails. We check the IP address and requested email of the password reset email request to ensure the
+combination has not requested a password reset email more than `password_reset_failure_threshold` times in the past
+`password_reset_failure_window ` days.
+
+Additionally, to protect against a distributed attack with requests to reset a single user's password coming from
+multiple IP address, we check to see if there have been `2 * password_reset_failure_threshold` password reset requests
+from _any_ IP addresses in the last `password_reset_failure_window` days.
+
+The maximum number of requests (default of 3 attempts) and the number of days back in which to check those requests (default 1 day) are configurable.
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/content_controller.yml`.
+2. To change the maximum number of attempts allowed, add `password_reset_failure_threshold: #` but replace `#` with
+ number of maximum attempts you wish to allow.
+3. To change the number of days in which password reset requests are checked against, add
+ `password_reset_failure_window: #` but replace `#` with the number of days back you wish to check. This value should
+ not exceed `30`.
+4. Save and exit.
+5. Run the playbooks to deploy your changes.
diff --git a/additional_doc/install.sh b/additional_doc/install.sh
new file mode 100755
index 0000000..1971d09
--- /dev/null
+++ b/additional_doc/install.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+brew install hugo
\ No newline at end of file
diff --git a/additional_doc/layouts/shortcodes/img.html b/additional_doc/layouts/shortcodes/img.html
new file mode 100644
index 0000000..487028a
--- /dev/null
+++ b/additional_doc/layouts/shortcodes/img.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/additional_doc/layouts/shortcodes/line_break.html b/additional_doc/layouts/shortcodes/line_break.html
new file mode 100644
index 0000000..7c9331b
--- /dev/null
+++ b/additional_doc/layouts/shortcodes/line_break.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/additional_doc/static/images/CC_Rustici_landscape_white.svg b/additional_doc/static/images/CC_Rustici_landscape_white.svg
new file mode 100644
index 0000000..b7ee124
--- /dev/null
+++ b/additional_doc/static/images/CC_Rustici_landscape_white.svg
@@ -0,0 +1,92 @@
+
+
+
diff --git a/additional_doc/themes/rustici-docs-hugo-theme b/additional_doc/themes/rustici-docs-hugo-theme
new file mode 160000
index 0000000..4b31be2
--- /dev/null
+++ b/additional_doc/themes/rustici-docs-hugo-theme
@@ -0,0 +1 @@
+Subproject commit 4b31be269a02d5202f82f8b66720e5a87a765af4
diff --git a/aws-s3.yml b/aws-s3.yml
new file mode 100644
index 0000000..88cd97a
--- /dev/null
+++ b/aws-s3.yml
@@ -0,0 +1,31 @@
+# This playbook:
+# - Creates S3 buckets
+# - Creates an IAM user for CloudFront to have access to the S3 buckets
+# - Enables S3 filestorage
+# - Writes the S3 group_vars file
+#
+# Requirements:
+# - Must be run on a Linux EC2 instance with an attached IAM role (with EC2 permissions)
+# or boto must be configured with AWS credentials
+#
+# Example Usage:
+# ansible-playbook aws-s3.yml -e "cloudfront_origin_access_identity=YOURCFORIGINID"
+---
+- hosts: localhost
+ vars_prompt:
+ - name: "S3FileStoragePrefix"
+ prompt: "Please enter a prefix for your S3 bucket's name. Your company name in a DNS-valid format is a good call here, i.e. 'rustici'"
+ default: "mycompany-cc-bucket"
+ private: no
+
+ - name: "S3AWSAccountIDPrompt"
+ prompt: "Please enter the AWS Account ID under which you want to create your bucket"
+ default: 123456789012
+ private: no
+
+ - name: "S3FileStorageIAMUsernamePrompt"
+ prompt: "Enter a username for Content Controller to use for accessing S3."
+ default: "mycompany-managed-cc-s3-user"
+ private: no
+ roles:
+ - aws-s3
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..1fd2093
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+#
+# Installs all of the necessary dependencies for running the Ansible playbooks. You should run this script
+# on your Ansible control server.
+#
+if [ "$(whoami)" != "root" ]; then
+ echo "You must use sudo to run this script."
+ exit -1
+fi
+
+# Get the original user for running commands that don't need sudo (such as pip)
+ORIGINAL_USER=${SUDO_USER:-${USERNAME:-unknown}}
+
+apt -y install python3 python3-dev python3-pip
+
+sudo -u ${ORIGINAL_USER} pip install Jinja2==3.0.3 ansible==2.8.6
+sudo -u ${ORIGINAL_USER} pip install boto boto3 botocore
diff --git a/build_ami.yml b/build_ami.yml
new file mode 100644
index 0000000..1d4df38
--- /dev/null
+++ b/build_ami.yml
@@ -0,0 +1,152 @@
+# This playbook:
+# - Creates a new SSH key pair locally for the ansible user on the app servers (if one does not exist)
+# - Starts a new EC2 instance
+# - Installs the SSH public key for the ansible user on the EC2 instance (and creates the ansible user)
+# - Installs Content Controller on the new EC2 instance
+# - Runs database migrations (can be skipped by adding `-e '{"cc_run_db_migrations": false}'`)
+# - Creates an AMI of the new EC2 instance and then shuts it down
+#
+# Requirements:
+# - Must be run on a Linux EC2 instance with an attached IAM role (with EC2 permissions)
+# or boto must be configured with AWS credentials
+# - All `group_vars` should be configured (including `aws.yml`)
+#
+# Example Usage:
+# ansible-playbook -e "env=prod" build_ami.yml
+
+- hosts: localhost
+ gather_facts: false
+ vars_files:
+ - group_vars/aws.yml
+ tasks:
+ - name: Generate Ansible SSH key pair
+ command: ssh-keygen -b 2048 -t rsa -f ~/.ssh/ansible -q -N ""
+ args:
+ creates: ~/.ssh/ansible.pub
+
+ - name: Read SSH public key
+ command: cat ~/.ssh/ansible.pub
+ register: ssh_public_key_output
+
+ - name: Launch temporary EC2 instance
+ ec2:
+ assign_public_ip: no
+ region: "{{ aws_region }}"
+ key_name: "{{ ssh_key }}"
+ group_id: "{{ ami_build_security_group_id }}"
+ instance_type: "{{ instance_size }}"
+ vpc_subnet_id: "{{ ami_build_vpc_subnet_id }}"
+ image: "{{ webami }}"
+ wait: yes
+ wait_timeout: 500
+ exact_count: 1
+ count_tag:
+ role: ami_builder
+ instance_tags:
+ Name: "Rustici CC AMI Builder"
+ role: ami_builder
+ environment: "{{ env }}"
+ user_data: |
+ #cloud-config
+ users:
+ - default
+ - name: ansible
+ lock_passwd: true
+ groups: [adm, audio, cdrom, dailout, dip, floppy, lxd, netdev, plugdev, sudo, video]
+ sudo: ["ALL=(ALL) NOPASSWD:ALL"]
+ shell: /bin/bash
+ ssh-authorized-keys:
+ - "{{ ssh_public_key_output.stdout }}"
+ register: ami_instance
+
+ - name: Wait for SSH to become available
+ wait_for:
+ port: 22
+ host: "{{ ami_instance.tagged_instances.0.private_ip }}"
+ timeout: 300
+ search_regex: OpenSSH
+
+ - name: Add host to group
+ add_host:
+ name: "Rustici CC AMI Builder"
+ ansible_host: "{{ ami_instance.tagged_instances.0.private_ip }}"
+ ansible_user: ansible
+ ansible_ssh_private_key_file: ~/.ssh/ansible
+ ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
+ ansible_python_interpreter: /usr/bin/python3
+ groups: just_created
+
+- hosts: just_created
+ become: true
+ force_handlers: true
+ gather_facts: false
+ vars:
+ building_ami: true
+ pre_tasks:
+ - name: Wait before running apt update
+ pause:
+ seconds: 30
+ # Install Ansible dependencies (most Ubuntu 18.04, CentOS 8, and Red Hat 8 images don't ship with these by default)
+ - name: Install Ansible dependencies, if necessary
+ raw: if [ -e /usr/bin/apt ]; then (apt -y update && export DEBIAN_FRONTEND=noninteractive && apt install -y python3 aptitude); fi; if [ -e /usr/bin/yum ]; then (yum -y update rpm && yum -y update && yum -y install python3); fi;
+
+ # Force gather_facts to run, now that python is installed
+ - setup:
+
+ # Now that gather_facts has run, set the python interpreter based on the OS
+ - name: Set ansible_python_interpreter
+ set_fact:
+ ansible_python_interpreter: "{{ '/usr/bin/python' if ansible_distribution_major_version == '7' else '/usr/bin/python3' }}"
+ vars_files:
+ - [ "group_vars/{{ env }}.yml", "group_vars/env.yml" ]
+ - group_vars/engine_java.yml
+ - group_vars/content_controller.yml
+ - group_vars/s3.yml
+ - group_vars/cloudfront.yml
+ - group_vars/keypair.yml
+ roles:
+ - common
+ - java
+ - { role: fips, when: fips_support_enabled == True }
+ - ssl
+ - tomcat
+ - mnt
+ - { role: mysql-local, when: cc_db_host == "localhost" }
+ - { role: mysql-config, when: initialize_mysql }
+ - apache
+ - content-controller
+ - cc-scorm-engine
+ - { role: cloudfront, when: use_cloudfront is defined and use_cloudfront|bool == True }
+ - { role: newrelic, when: newrelic_license_key is defined }
+ - { role: saml, when: enable_saml is defined and enable_saml|bool == True }
+
+- hosts: localhost
+ vars_files:
+ - [ "group_vars/{{ env }}.yml", "group_vars/env.yml" ]
+ - group_vars/aws.yml
+ tasks:
+ - name: Read build name
+ set_fact:
+ build_name: "{{ hostvars['Rustici CC AMI Builder']['build_name'] }}"
+
+ - name: Bundle AMI
+ ec2_ami:
+ instance_id: "{{ ami_instance.tagged_instances.0.id }}"
+ region: "{{ aws_region }}"
+ state: present
+ description: This was provisioned by Build AMI on {{ ansible_date_time.iso8601 }}
+ name: "{{ build_name }}_{{ env }}_{{ ansible_date_time.epoch }}"
+ tags:
+ Name: "{{ build_name }}-{{ ServerName }}"
+ environment: "{{ env }}"
+ domain: "{{ ServerName }}"
+ build_name: "{{ build_name }}"
+ created_by: "Rustici CC AMI Builder"
+ wait: yes
+ register: amioutput
+
+ - name: Terminate temporary instance
+ ec2:
+ state: absent
+ region: "{{ aws_region }}"
+ instance_ids: "{{ ami_instance.tagged_instances.0.id }}"
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 0000000..28f2a64
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,5 @@
+To install Hugo, run `./install.sh`.
+
+To run the Hugo docs locally, run `hugo server -D`.
+
+To generate the static files, just run `hugo` from this directory. Then all of the static files will be generated into the `public` directory.
\ No newline at end of file
diff --git a/doc/archetypes/default.md b/doc/archetypes/default.md
new file mode 100644
index 0000000..00e77bd
--- /dev/null
+++ b/doc/archetypes/default.md
@@ -0,0 +1,6 @@
+---
+title: "{{ replace .Name "-" " " | title }}"
+date: {{ .Date }}
+draft: true
+---
+
diff --git a/doc/config.yaml b/doc/config.yaml
new file mode 100644
index 0000000..a57b343
--- /dev/null
+++ b/doc/config.yaml
@@ -0,0 +1,24 @@
+baseURL: "https://docs.contentcontroller.com/self-hosting/"
+languageCode: "en-us"
+title: "Content Controller"
+theme: "rustici-docs-hugo-theme"
+
+menu:
+ main:
+ - identifier: aws
+ name: "AWS"
+ weight: 6
+
+ - identifier: faq
+ name: "FAQ"
+ weight: 7
+
+ - identifier: release-notes
+ name: "Release Notes"
+ url: "https://support.scorm.com/hc/en-us/sections/115000419513-Release-Notes"
+ weight: 8
+
+ - identifier: knowledge-base
+ name: "Knowledge Base"
+ url: "https://support.scorm.com/hc/en-us/categories/201445406-Content-Controller"
+ weight: 9
diff --git a/doc/content/_index.md b/doc/content/_index.md
new file mode 100644
index 0000000..58955fa
--- /dev/null
+++ b/doc/content/_index.md
@@ -0,0 +1,23 @@
+---
+title: "Introduction"
+type: docs
+menu: "main"
+---
+
+## Where to Start
+
+The [Requirements](/self-hosting/requirements) page describes what you need to install Content Controller.
+
+[Hosting at Scale](/self-hosting/infrastructure) describes the infrastructure and architectural design that we recommend for hosting the application.
+
+To help you understand the process, [Deploy Scripts and Tools](/self-hosting/deploy-tools) is an in-depth breakdown of each piece of our deployment.
+
+The [Installation Guide](/self-hosting/quick-start) contains the actual step-by-step instructions for how to set-up your playbooks and run the deployment.
+
+For in-depth descriptions of hosting Content Controller in AWS, refer to the [Deploying in AWS](/self-hosting/aws/aws) section.
+
+## Getting Support
+
+As you prepare to deploy your environment, please remember that we are here to help! Team Delight is all about finding ways to make things work for you - don't hesitate to give us a call if you have questions or need advice!
+
+Email: support@scorm.com Phone: 866.497.2676
diff --git a/doc/content/aws/_index.html b/doc/content/aws/_index.html
new file mode 100644
index 0000000..62e0527
--- /dev/null
+++ b/doc/content/aws/_index.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/doc/content/aws/ami.md b/doc/content/aws/ami.md
new file mode 100644
index 0000000..bb30c35
--- /dev/null
+++ b/doc/content/aws/ami.md
@@ -0,0 +1,111 @@
+---
+title: "AMI"
+type: docs
+menu:
+ main:
+ name: 10. Deploying Content Controller
+ identifier: ami
+ parent: aws
+ weight: 11
+---
+
+# Deploying Content Controller
+
+The best way to deploy Content Controller is to start with a blank server, provision it with the Ansible scripts, image it, and then deploy as many servers as you need from that image. This makes rollbacks easier, ensures you have a reproducable server state, allows you to setup an auto-scaling group later on, and in many cases will allow you to do 0 downtime upgrades. If you're using Amazon Web Services, we've provided a playbook to automate this task for you. Unless you have a really good reason not to, you should use this method.
+
+## Running the Build AMI Playbooks
+
+`build_ami.yml` will
+* Generate an SSH key pair for Ansible to use (if needed)
+* Create a blank Ubuntu instance and provision an Ansible user with sudo powers
+* Run the Ansible playbooks against it
+* **Run database upgrades against your production DB** (this can be turned off if needed, but you'll have to run them before deploying the AMI)
+* Create an Amazon Machine Image of the server
+* Destroy the temporary server
+
+Before getting started, we recommend making an image of your RDS instance, so you can rollback if needed. It's also a good idea to do this during a maintenance window. Before running this playbook, you should have completed all of the steps in this guide, and you should be sure that your `group_vars` settings are correct.
+
+1. Go to the `ContentController-PublicDeploy` folder on your Ansible control server.
+2. Run the playbook:
+```
+ansible-playbook -e "env=prod" build_ami.yml
+```
+
+## Deploying an AMI
+
+### Deploying an AMI Manually
+
+Go to **Services** -> **EC2** in AWS.
+
+1. Click **Instances** on the left sidebar, and click **Launch Instance**.
+2. Select the **My AMIs* tab.
+3. Click **Select** beside `ContentController-v.v.v_env_xxxxxxxx` (the one corresponding to the AMI you would like to deploy).
+4. Choose an instance size (t2.medium or larger) and click **Next: Configure Instance Details**.
+5. Choose `Rustici CC VPC` for your Network, `CC Private 1` for your Subnet, `Disable` for Auto-assign Public IP, `None` for the IAM role, disable protect against accidental termination, enable T2 unlimited if you'd like, and click **Next: Add Storage**.
+6. Enter a value for the root volume size (100 GB is probably good) and check Delete on Termination. Click **Next: Add Tags**.
+7. Click **Add Tag**. Enter `Name` for the Key and `CC App` for the Value. Click **Next: Configure Security Group**.
+8. Choose **Select an existing security group** and check the box beside the `CC Application` security group you created earlier.
+9. Click **Review and Launch**.
+10. Click **Launch**.
+11. Select **Choose an existing key pair** and select the `rustici-cc` key pair that you created earlier. Check the **I acknowledge...** box, and then click **Launch Instances**.
+12. Select the instance in the **Instances** list and in the **Description** tab, copy the **Private IP** address.
+13. Repeat steps 1-12, but this time choose `CC Private 2` for your Subnet. This way you'll have at least 1 server in each availability zones.
+14. Click **Target Groups** on the left sidebar and select your `cc-prod-target-group`.
+15. Click on the **Targets** tab, and click the blue **Edit** button.
+16. Select the 2 (or more) application servers that you just launched, click **Add to registered**, and click **Save**.
+17. Once you're sure your servers are up, click **Edit** again, check the boxes beside the old app servers, click **Remove**, and click **Save**.
+18. Go back to **Instances** and either stop or terminate your old app servers.
+
+### Deploying an AMI with Auto Scaling Groups
+
+If you're using AWS EC2 auto scaling, you can create a [launch configuration](https://docs.aws.amazon.com/autoscaling/ec2/userguide/LaunchConfiguration.html) with your newly built Amazon Machine Image. Typically, you would have 2 [auto scaling groups](https://docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroup.html) (maybe a blue group and a green group) that register their EC2 instances in your load balancer's target group. When you're upgrading, you can apply a new launch configuration to the inactive auto scaling group, and change your policies to scale it up from `0` instances to `X` instances. Once the new group's instances are reporting healthy, you can set the previous auto scaling group to scale it down to `0` instances so that it takes the old application servers out of service. This gives you an easy way to roll out with no downtime, and an easy way to rollback if something goes sideways.
+
+Our MH team has some CloudWatch alarms in place that are monitoring the average CPU utilization of the entire ASG. In order to monitor the average CPU utilization of the entire ASG, you will need to enable ASG metrics when creating the group. Here are the configurations our MH team uses for their ASG rules:
+
+
+Auto scaling can be complicated to get configured correctly, so if you're going to set it up, give us a call. Based on your unique environment, we can give you some rules to use as a starting point.
+
+## Alternative Deploy Method
+
+### Deploying Straight to Static Server(s)
+
+If you're using bare metal or you just really don't want to build AMIs and deploy them, then you can run the playbooks directly against the application servers. Note that you should still make a backup of some sort, and you'll likely have some downtime.
+
+1. Setup your application servers. These should be a base Ubuntu 14.04 or later install. Take note of their IPs, the SSH user name, and the SSH key.
+2. Go to the `ContentController-PublicDeploy` folder on your control server.
+3. Copy `inventory` to `inventory.prod`.
+4. Edit the inventory and delete the dummy IP addresses replacing them with the private/internal IP addresses of your application servers.
+5. Run the Ansible playbooks against your production servers:
+```
+ansible-playbook --user=YOUR_SSH_USER --connection=ssh --inventory-file=inventory.prod env.yml
+```
+
+**Note:** you may need to add `--ask-sudo-pass` or `--private-key="~/.ssh/my_private_key.pem"` to get SSH to work.
+
+## Upgrading to the Latest Content Controller Version
+
+1. Take a look at the [Release Notes](https://support.scorm.com/hc/en-us/sections/115000419513-Release-Notes) to see if there are any important messages about self-hosting for any of the versions between the one you're starting on and the one you're moving to.
+2. Go to the `ContentController-PublicDeploy` folder on your Ansible server.
+3. Create an image of your DB and control server in case something goes sideways during the upgrade.
+4. Run `git pull`, then run `git checkout vx.x.xxx` replacing `x.x.xxx` with the version number you are moving to.
+5. Follow the steps above in **Running the Build AMI Playbooks** and **Deploying an AMI to Production** or **Alternative Deploy Method**.
+
+## Customizing your AMI
+
+Sometimes, you'll want to customize the image used for building your application server. A good use case for this would be to add your own infrastructure monitoring daemons, log aggregation utilities, or server hardening settings.
+
+1. Launch an Ubuntu 18.04 EC2 instance.
+2. Connect to it, install updates (`apt update && apt upgrade -y`), and apply your customizations.
+3. Create an AMI of the instance (and take note of the AMI ID once it is complete).
+4. Terminate the EC2 instance.
+5. On your Ansible server, navigate to `ContentController-PublicDeploy` and edit `group_vars/aws.yml`.
+6. Set `webami: ami-********` to `webami: ami-your_custom_ami_id`.
+7. Follow the instructions above for running the build AMI playbooks.
+
+## Resources
+
+[Amazon Machine Images](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html)
+
+[AWS EC2 Auto Scaling](https://docs.aws.amazon.com/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html)
+
+[EC2 Launch Configuration](https://docs.aws.amazon.com/autoscaling/ec2/userguide/LaunchConfiguration.html)
diff --git a/doc/content/aws/aws.md b/doc/content/aws/aws.md
new file mode 100644
index 0000000..74e6fe4
--- /dev/null
+++ b/doc/content/aws/aws.md
@@ -0,0 +1,20 @@
+---
+title: "AWS"
+type: docs
+menu:
+ main:
+ name: AWS Overview
+ identifier: aws_overview
+ parent: aws
+ weight: 1
+---
+
+# Initial Infrastructure Setup
+
+This guide will go step-by-step through deploying Content Controller in a high availability setup to Amazon Web Services. If you already know your way around AWS, feel free to just use this as a reference, and follow your own practices. If you prefer to save some money and only deploy to one availability zone, that's fine - just skip the multi-AZ settings.
+
+Create an AWS account. If you already have one, we'll create new Virtual Private Cloud for Content Controller to operate from. Before you begin, make sure you have your [AWS account number](https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html), your `keypair.yml` file (provided by Rustici), and access to your domain's DNS records.
+
+## AWS Account Security
+
+Take some time to review the [IAM Best Practices guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). It's a good idea to [lock down your root account](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#lock-away-credentials) and use an IAM user for yourself. Also, consider using [AWS multi-factor authentication](https://aws.amazon.com/iam/details/mfa/) along with [Authy](https://authy.com/) or Google Authenticator.
diff --git a/doc/content/aws/certificate-manager.md b/doc/content/aws/certificate-manager.md
new file mode 100644
index 0000000..73afb8f
--- /dev/null
+++ b/doc/content/aws/certificate-manager.md
@@ -0,0 +1,29 @@
+---
+title: "Certificate Manager"
+type: docs
+menu:
+ main:
+ name: 3. Certificate Manager
+ identifier: certificate_manager
+ parent: aws
+ weight: 4
+---
+
+# Certificate Manager
+
+Certificate Manager will keep track of your SSL certificates and make them available to AWS services (such as the load balancer). You may either request a free certificate from AWS or import an existing certificate.
+
+Go to **Services** -> **Certificate Manager**
+
+## Request a certificate
+
+1. Click on **Request a certificate**.
+2. Under domain name, enter your subdomain for Content Controller. If you are dedicating an entire domain to your CC install (such as `example.com`), make sure you enter both `www.example.com` and `example.com` OR `*.example.com` and `example.com`. Click **Next**.
+3. Choose **DNS validation** and click **Review**, then click **Confirm and request**.
+4. Expand the section(s) with your domain name and click **Create record in Route 53**, then in the dialog that appears, click **Create**.
+5. Click **Continue**.
+6. It may take a few minutes for your certificate to be validated.
+
+## Resources
+
+[What Is AWS Certificate Manager?](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html)
diff --git a/doc/content/aws/cloudfront.md b/doc/content/aws/cloudfront.md
new file mode 100644
index 0000000..0c5c943
--- /dev/null
+++ b/doc/content/aws/cloudfront.md
@@ -0,0 +1,137 @@
+---
+title: "CloudFront"
+type: docs
+menu:
+ main:
+ name: 9. CloudFront
+ identifier: cloudfront
+ parent: aws
+ weight: 10
+---
+
+# CloudFront
+
+Cloudfront acts like a reverse proxy in front of your Content Controller Server and the S3 bucket that you use for storing course content. For most connections, it just passes the request to your web tier. For requests for course content, CloudFront authenticates the user via a signed cookie that the application generates, and if the user is allowed, it serves up the content from your S3 bucket.
+
+To get started, you need to have completed _at least_ the [Certificate Manager](/self-hosting/aws/certificate-manager), [S3](/self-hosting/aws/s3), and [Load Balancer and Target Group](/self-hosting/aws/load-balancer) steps in the [Deploying in AWS](/self-hosting/aws/aws) section before this one, and it will be easiest if you have completed _all_ of the steps.
+
+## Signing Keys
+
+We use signed cookies to authenticate requests for content that lives in S3. In order to make this work, you’ll need to generate a pair of signing keys and make them available to the application.
+
+* [Generate your signing keys per this AWS doc](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html) and note the Access Key ID for the keypair.
+* Convert the private key to DER format so that Java can read it:
+```
+openssl pkcs8 -topk8 -nocrypt -in pk-********************.pem -inform PEM -out pk-********************.der -outform DER
+```
+
+## Ansible Config
+
+* Connect to your Ansible server and go to the `ContentController-PublicDeploy` folder.
+* Run `mkdir -p roles/cloudfront/files` to create the files folder, if it doesn't already exist.
+* Place the `pk-********************.der` and `public-********************.pem` files (generated above) in the `roles/cloudfront/files` folder.
+* Edit the `group_vars/cloudfront.yml` file and make these changes.
+ * Make sure that `use_cloudfront` is set to `true`.
+ * Set your **Access Key ID** (created above) for the `cloudfront_access_key_id` variable.
+ * Add the filename (without the path) for your key files similar to this:
+ ```
+ cloudfront_private_key: pk-********************.der
+ cloudfront_public_key: public-********************.pem
+ ```
+
+## Create your Distribution
+
+Go to **Services** -> **CloudFront**
+
+### Create a Distribution for Content Controller
+
+1. Select the **Distributions** tab on the left, and click the blue **Create Distribution** button.
+2. For **Origin**, enter these values:
+ * Origin domain: `cc-load-balancer-*****` (choose the load balancer you created earlier)
+ * Origin Path: leave it blank
+ * Origin name: should be auto-filled when you select your load balancer above
+ * Minimum origin SSL Protocol: **TLSv1.2**
+ * Origin Protocol Policy: **HTTPS Only***
+ * Origin Response Timeout: 60
+ * Origin Keep-alive Timeout: 5
+ * HTTP Port: 80
+ * HTTPS Port: 443
+
{{< img src="/self-hosting/aws/img/cf-origin-settings.png" >}}
+4. For **Default Cache Behavior**, enter the following values:
+ * Compress Objects Automatically: **No**
+ * Viewer Protocol Policy: **Redirect HTTP to HTTPS**
+ * Allowed HTTP Methods: **GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE**
+ * Restrict Viewer Access: **No**
+ * Smooth streaming: **No**
+ * Enable real-time logs: **No**
+ * Cache key and origin requests: **Legacy cache settings** configured as follows:
+
{{< img src="/self-hosting/aws/img/cf-legacy-cache-settings.png" >}}
+4. For **Settings**, enter the following values:
+ * Price Class: (Choose what you'd like here - we use **Use only North America and Europe**)
+ * AWS WAF Web ACL: **None**
+ * Alternate Domain Names: Enter your cc domain name here (example: `demo.contentcontroller.net`)
+ * SSL Certificate: **Custom SSL Certificate**, and then choose the [certificate you created earlier](/self-hosting/aws/certificate-manager)
+ * Legacy clients support: **Only Clients that Support Server Name Indication**
+ * Security Policy: Choose **TLSv1** or the **TLSv1.2_2021** if you prefer security over browser support. For more details see this AWS documentation on [Supported Protocols and Ciphers](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html).
+ * Supported HTTP Versions: **HTTP/2**
+ * Default Root Object: blank
+ * Logging: **On**
+ * Bucket for Logs: Choose the bucket created in the [S3](/self-hosting/aws/s3) config earlier with the suffix `-logs` (example: `rustici-demo-cc-content-logs.s3.amazonaws.com`)
+ * Log Prefix: `cloudfront`
+ * Cookie Logging: **Off**
+ * Enable IPv6: **Off**
+
{{< img src="/self-hosting/aws/img/cf-final-settings.png" >}}
+6. Click **Create Distribution**.
+
+### Create an S3 Origin
+
+1. On the **CloudFront Distributions** page, click the ID of the distribution you just created.
+2. Click on the **Origins** tab, and click the blue **Create Origin** button.
+3. For **Origin Settings**, enter these values
+ * Origin Domain Name: Choose the bucket created in the [S3](/self-hosting/aws/s3) config earlier with the suffix `-content` (example: `rustici-demo-cc-content.s3.amazonaws.com`)
+ * Origin Path: leave blank
+ * Name: should be auto-filled when you select the S3 bucket above
+ * Origin access control: **Yes**
+ * Origin Access Identity: **Use an Existing Identity**, then choose the OAI you created earlier in the [S3](/self-hosting/aws/s3) step
+ * Grant Read Permissions on Bucket: **Yes, Update Bucket Policy**
+4. Click **Create**.
+
+### Create S3 Origin Behavior
+
+1. On the **CloudFront Distributions** page, click the ID of the distribution you just created.
+2. Click on the **Behaviors** tab, and click the blue **Create Behavior** button.
+3. For **Settings**, enter these values
+ * Path Pattern: `/courses/*`
+ * Origin: Choose your S3 origin you created above (example: `S3-rustici-demo-cc-content`)
+ * Compress Objects Automatically: **No**
+ * Viewer Protocol Policy: **HTTPS Only**
+ * Allowed HTTP Methods: **GET, HEAD, OPTIONS**
+ * Cached HTTP Methods: leave unchecked
+ * Restrict viewer access: **Yes**
+ * Smooth Streaming: **No**
+ * Cache key and origin requests: **Cache policy and origin request policy**, create a cache policy using the following screenshot as a guide:
+
{{< img src="/self-hosting/aws/img/cf-behavior-cache-policy.png" >}}
+
+4. Click **Create**.
+
+### Add CloudFront to Route 53
+
+You'll want to be able to access Content Controller through your own CNAME instead of using the URL for the CloudFront endpoint. To allow that, we'll use Route53.
+
+Go to **Services** -> **Route 53**
+
+1. Click on **Hosted zones** on the left sidebar.
+2. Click on your Public hosted zone created earlier in the [Route 53](/self-hosting/aws/route-53) step. (example: `demo.contentcontroller.net`)
+3. Click **Create Record Set**.
+4. In the **Create Record Set** sidebar, enter the following values
+ * Name: Enter the subdomain you wish to host CC on (example: `cc`)
+ * Record type: **CNAME**
+ * Alias: **Disabled**
+ * Value: should be your CloudFront distribution created above
+5. Click **Create**.
+
+## Resources
+
+[Routing Traffic to an Amazon CloudFront Web Distribution by Using Your Domain Name](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html)
+
+[Private Content Trusted Signers](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html)
diff --git a/doc/content/aws/ec2-ansible.md b/doc/content/aws/ec2-ansible.md
new file mode 100644
index 0000000..7d27a28
--- /dev/null
+++ b/doc/content/aws/ec2-ansible.md
@@ -0,0 +1,85 @@
+---
+title: "EC2 Bastion / Ansible Controller"
+type: docs
+menu:
+ main:
+ name: 5. EC2 Bastion / Ansible Controller
+ identifier: ec2_ansible
+ parent: aws
+ weight: 6
+---
+
+# EC2 Bastion / Ansible Controller
+
+The first server you will need is a publicly accessible bastion host. This server will go in the public subnet, have a public IP, and be accessible through SSH. You can tunnel through this machine to access your databases, and you can host your Ansible playbooks on this server. **You should pay careful attention to make sure this server stays up to date and is well-secured.** Make a backup of it once you're finished with the initial setup. It will contain your application secrets, passwords, and configs.
+
+Go to **Services** -> **EC2**
+
+## Create an Instance, Security Group, and Key Pair
+
+1. Click on **Instances** on the left sidebar and click the blue **Launch Instance** button.
+2. Click **Select** by the instance type to use. Any linux should work for this box, but we recommend **Ubuntu Server 18.04 LTS (HVM)**, and the rest of these instructions (such as bootstrapping) will assume that is the instance type you chose.
+3. Choose **t2.micro** as the instance type, and click **Next: Configure Instance Details**.
+4. Choose `Rustici CC VPC` for your Network, `CC Public 1` for your Subnet, `Enable` for Auto-assign Public IP, `RusticiCCAnsibleRole` for the IAM role, enable protect against accidental termination, enable T2 unlimited if you'd like, and click **Next: Add Storage**.
**WARNING**: Be aware of what you're doing here! By attaching credentials (`RusticiCCAnsibleRole`) to this instance that have the power to create IAM roles, servers, etc, if this machine is compromised, the attacker will have the ability to create new user accounts and do anything they want in your AWS account. Make sure you keep this server secure!!
+5. Enter a value for the root volume size (16 GB is probably good) and uncheck Delete on Termination. Click **Next: Add Tags**.
+6. Click **Add Tag**. Enter `Name` for the Key and `CC Ansible` for the Value. Click **Next: Configure Security Group*.
+7. Choose **Select an existing security group** and check the box beside the `CC Ansible` security group you created earlier.
+8. Click **Review and Launch**.
+9. Click **Launch**.
+10. In the dialog choose **Create a new key pair** and name it `rustici-cc`. Click **Download Key Pair**. Save this key pair in a safe, private place, and back it up. You can't get into this server without it.
+11. Click **Launch Instances**.
+12. You should see your new `CC Ansible` instance in the list of instances. Select it and copy the IPv4 Public IP address from the description tab. This is the IP address you will use to connect to your server.
+
+### Create a DNS record in Route 53
+
+If you don't want to remember the IP address to your Ansible control server, you can create an A record for it in Route 53. For our demo, we'll create `ansible.demo.contentcontroller.net`.
+
+1. Go to **Services** -> **Route 53**.
+2. Select **Hosted zones** on the left sidebar.
+3. Select your domain.
+4. Click the blue **Create Record Set** button.
+5. In the sidebar that appears, enter your desired subdomain **Name** (such as `ansible`), and choose `A - IPv4 address` for the **Type**.
+6. Enter the IPv4 Public IP address from step 12 above in the **Value** box.
+7. Click **Create**.
+
+## Prepare your SSH key
+
+We're assuming that you are using macOS or Linux for this step. If you are using Windows, please refer to the documentation for your SSH client (such as Putty).
+
+1. Copy the `rustici-cc.pem` key pair that you downloaded to `~/.ssh/rustici-cc.pem`.
+2. Change the permissions by running `chmod 0600 ~/.ssh/rustici-cc.pem`.
+
+## Bootstrap Ansible Controller
+
+This will download all of the Ansible playbooks for installing Content Controller and install Ansible and its dependencies.
+
+1. SSH to your Ansible instance. On macOS or Linux, you can do this by running `ssh -i ~/.ssh/rustici-cc.pem ubuntu@ansible.demo.contentcontroller.net`.
+2. Update the server by running `sudo apt update && sudo apt upgrade -y`. If you are prompted about updating grub, choose **keep the local version currently installed**.
+3. Make sure git is installed by running `sudo apt install -y git`.
+4. Pick a place to store Content Controller's deploy files, secrets, etc. Your home directory is fine, unless you plan on adding additional user accounts to this machine. Run `cd ~` to get there.
+5. Clone the deploy file repository by running `git clone https://github.com/RusticiSoftware/ContentController-PublicDeploy.git`.
+6. Go to the newly cloned directory. `cd ContentController-PublicDeploy`
+7. Run `sudo ./bootstrap.sh` to install the necessary dependencies for running the Ansible playbooks.
+
+## Bootstrap Content Controller secrets and config
+
+This will generate application passwords, database passwords, and other sensible defaults.
+
+1. SSH to your Ansible instance and go to your `ContentController-PublicDeploy` directory.
+2. Run `nano group_vars/keypair.yml`. Paste the contents of the `keypair.yml` file provided for you by Rustici Software. Press CTRL + X to exit, and press `Y` and then press ENTER to save.
+3. Run `./setup.sh demo.contentcontroller.net`, but substitute your own domain. Type `YES` to confirm, and press enter.
+
+### Edit group_vars
+
+Now, you'll need to set some configuration parameters to match your environment. If you haven't already, take a look at the [Deploy Tools](/self-hosting/deploy-tools) to see what the host_vars and group_vars are all about. Then, edit these files as needed for your installation.
+
+If you're following this example exactly, then you'll need to make at least these changes:
+
+#### `group_vars/env.yml`
+
+* Change `env: dev` to `env: prod`
+* Change `S3FileStorageEnabled: False` to `S3FileStorageEnabled: true`
+* Change `mysql_root_user: root` to `mysql_root_user: ccroot`
+* Make note of the `mysql_root_user` and `mysql_root_password`. You will need those when provisioning the RDS instance.
+* Change `use_ssl: true` to `use_ssl: false` and `allow_80: false` to `allow_80: true`. We'll be terminating SSL at the load balancer.
+* If your application server does not have exactly 4 GB RAM, then you'll probably need to adjust the heap sizes.
diff --git a/doc/content/aws/iam.md b/doc/content/aws/iam.md
new file mode 100644
index 0000000..789a03c
--- /dev/null
+++ b/doc/content/aws/iam.md
@@ -0,0 +1,33 @@
+---
+title: "Content Controller"
+type: docs
+menu:
+ main:
+ name: 4. IAM
+ identifier: iam
+ parent: aws
+ weight: 5
+---
+
+# IAM
+
+IAM manages credentials for you AWS account. It can create users, API credentials, etc. We'll need to provision an IAM role for the next few steps. It's easiest to create this role and attach it to your Ansible EC2 instance, however you can also create API credentials and configure boto on your Ansible box manually. We'll create a role here, and then we'll attach it to the Ansible box in the next section.
+
+Go to **Services** -> **IAM**
+
+## Create IAM role
+
+1. Click on **Roles** on the left sidebar and click the blue **Create role** button.
+2. Choose **AWS service** and **EC2**, then click **Next: Permissions**.
+3. Attach the following policies by checking the boxes beside them:
+ 1. AmazonS3FullAccess
+ 2. AmazonEC2FullAccess
+ 3. IAMFullAccess
+4. Click **Next: Review**
+5. Enter `RusticiCCAnsibleRole` for the Name and `Allows the Rustici Content Controller Ansible instance to provision users, servers, and S3 buckets.` for the Description, and click **Create role**.
+
+## Resources
+
+[What Is IAM?](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html)
+
+[IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
diff --git a/doc/content/aws/img/asg-rules.png b/doc/content/aws/img/asg-rules.png
new file mode 100644
index 0000000..03d37a9
Binary files /dev/null and b/doc/content/aws/img/asg-rules.png differ
diff --git a/doc/content/aws/img/cf-behavior-cache-policy.png b/doc/content/aws/img/cf-behavior-cache-policy.png
new file mode 100644
index 0000000..000c32c
Binary files /dev/null and b/doc/content/aws/img/cf-behavior-cache-policy.png differ
diff --git a/doc/content/aws/img/cf-cache-behavior.png b/doc/content/aws/img/cf-cache-behavior.png
new file mode 100644
index 0000000..e13946f
Binary files /dev/null and b/doc/content/aws/img/cf-cache-behavior.png differ
diff --git a/doc/content/aws/img/cf-distribution-settings.png b/doc/content/aws/img/cf-distribution-settings.png
new file mode 100644
index 0000000..9a4f047
Binary files /dev/null and b/doc/content/aws/img/cf-distribution-settings.png differ
diff --git a/doc/content/aws/img/cf-final-settings.png b/doc/content/aws/img/cf-final-settings.png
new file mode 100644
index 0000000..4951795
Binary files /dev/null and b/doc/content/aws/img/cf-final-settings.png differ
diff --git a/doc/content/aws/img/cf-legacy-cache-settings.png b/doc/content/aws/img/cf-legacy-cache-settings.png
new file mode 100644
index 0000000..6e2bb3d
Binary files /dev/null and b/doc/content/aws/img/cf-legacy-cache-settings.png differ
diff --git a/doc/content/aws/img/cf-oai-comment.png b/doc/content/aws/img/cf-oai-comment.png
new file mode 100644
index 0000000..b33efd5
Binary files /dev/null and b/doc/content/aws/img/cf-oai-comment.png differ
diff --git a/doc/content/aws/img/cf-origin-settings.png b/doc/content/aws/img/cf-origin-settings.png
new file mode 100644
index 0000000..244f16b
Binary files /dev/null and b/doc/content/aws/img/cf-origin-settings.png differ
diff --git a/doc/content/aws/img/cf-s3-cache-behavior.png b/doc/content/aws/img/cf-s3-cache-behavior.png
new file mode 100644
index 0000000..eb51b79
Binary files /dev/null and b/doc/content/aws/img/cf-s3-cache-behavior.png differ
diff --git a/doc/content/aws/img/cf-s3-origin-settings.png b/doc/content/aws/img/cf-s3-origin-settings.png
new file mode 100644
index 0000000..bdc2812
Binary files /dev/null and b/doc/content/aws/img/cf-s3-origin-settings.png differ
diff --git a/doc/content/aws/img/cm-domain-name.png b/doc/content/aws/img/cm-domain-name.png
new file mode 100644
index 0000000..2ec34a9
Binary files /dev/null and b/doc/content/aws/img/cm-domain-name.png differ
diff --git a/doc/content/aws/img/cm-validation.png b/doc/content/aws/img/cm-validation.png
new file mode 100644
index 0000000..7c7be2f
Binary files /dev/null and b/doc/content/aws/img/cm-validation.png differ
diff --git a/doc/content/aws/img/ec2-create-ansible-config.png b/doc/content/aws/img/ec2-create-ansible-config.png
new file mode 100644
index 0000000..2b3d83d
Binary files /dev/null and b/doc/content/aws/img/ec2-create-ansible-config.png differ
diff --git a/doc/content/aws/img/ec2-trg-add-instances.png b/doc/content/aws/img/ec2-trg-add-instances.png
new file mode 100644
index 0000000..68b4cfb
Binary files /dev/null and b/doc/content/aws/img/ec2-trg-add-instances.png differ
diff --git a/doc/content/aws/img/elb-create-basic-config.png b/doc/content/aws/img/elb-create-basic-config.png
new file mode 100644
index 0000000..cd8bed0
Binary files /dev/null and b/doc/content/aws/img/elb-create-basic-config.png differ
diff --git a/doc/content/aws/img/elb-create-routing.png b/doc/content/aws/img/elb-create-routing.png
new file mode 100644
index 0000000..7334be9
Binary files /dev/null and b/doc/content/aws/img/elb-create-routing.png differ
diff --git a/doc/content/aws/img/elb-create-security.png b/doc/content/aws/img/elb-create-security.png
new file mode 100644
index 0000000..d71f907
Binary files /dev/null and b/doc/content/aws/img/elb-create-security.png differ
diff --git a/doc/content/aws/img/r53-hosted-zone.png b/doc/content/aws/img/r53-hosted-zone.png
new file mode 100644
index 0000000..eaa2c8e
Binary files /dev/null and b/doc/content/aws/img/r53-hosted-zone.png differ
diff --git a/doc/content/aws/img/rds-param-group-details.png b/doc/content/aws/img/rds-param-group-details.png
new file mode 100644
index 0000000..a0272b1
Binary files /dev/null and b/doc/content/aws/img/rds-param-group-details.png differ
diff --git a/doc/content/aws/img/rds-subnet-group.png b/doc/content/aws/img/rds-subnet-group.png
new file mode 100644
index 0000000..32dee84
Binary files /dev/null and b/doc/content/aws/img/rds-subnet-group.png differ
diff --git a/doc/content/aws/img/rtb-list.png b/doc/content/aws/img/rtb-list.png
new file mode 100644
index 0000000..3875f17
Binary files /dev/null and b/doc/content/aws/img/rtb-list.png differ
diff --git a/doc/content/aws/img/rtb-public-association.png b/doc/content/aws/img/rtb-public-association.png
new file mode 100644
index 0000000..bd7ea24
Binary files /dev/null and b/doc/content/aws/img/rtb-public-association.png differ
diff --git a/doc/content/aws/img/rtb-public-routes.png b/doc/content/aws/img/rtb-public-routes.png
new file mode 100644
index 0000000..51ff6af
Binary files /dev/null and b/doc/content/aws/img/rtb-public-routes.png differ
diff --git a/doc/content/aws/img/vpc-create.png b/doc/content/aws/img/vpc-create.png
new file mode 100644
index 0000000..4022eb1
Binary files /dev/null and b/doc/content/aws/img/vpc-create.png differ
diff --git a/doc/content/aws/img/vpc-diagram.png b/doc/content/aws/img/vpc-diagram.png
new file mode 100644
index 0000000..54d47b3
Binary files /dev/null and b/doc/content/aws/img/vpc-diagram.png differ
diff --git a/doc/content/aws/img/vpc-igw-attach.png b/doc/content/aws/img/vpc-igw-attach.png
new file mode 100644
index 0000000..51a9a84
Binary files /dev/null and b/doc/content/aws/img/vpc-igw-attach.png differ
diff --git a/doc/content/aws/img/vpc-region-selection.png b/doc/content/aws/img/vpc-region-selection.png
new file mode 100644
index 0000000..233e90f
Binary files /dev/null and b/doc/content/aws/img/vpc-region-selection.png differ
diff --git a/doc/content/aws/img/vpc-rtb-create-private.png b/doc/content/aws/img/vpc-rtb-create-private.png
new file mode 100644
index 0000000..388e64c
Binary files /dev/null and b/doc/content/aws/img/vpc-rtb-create-private.png differ
diff --git a/doc/content/aws/img/vpc-rtb-public-routes.png b/doc/content/aws/img/vpc-rtb-public-routes.png
new file mode 100644
index 0000000..a9d2c78
Binary files /dev/null and b/doc/content/aws/img/vpc-rtb-public-routes.png differ
diff --git a/doc/content/aws/img/vpc-rtb-public-subnet-assc.png b/doc/content/aws/img/vpc-rtb-public-subnet-assc.png
new file mode 100644
index 0000000..9d4c3ff
Binary files /dev/null and b/doc/content/aws/img/vpc-rtb-public-subnet-assc.png differ
diff --git a/doc/content/aws/img/vpc-sg-inbound-db.png b/doc/content/aws/img/vpc-sg-inbound-db.png
new file mode 100644
index 0000000..dea7cd4
Binary files /dev/null and b/doc/content/aws/img/vpc-sg-inbound-db.png differ
diff --git a/doc/content/aws/img/vpc-sg-list.png b/doc/content/aws/img/vpc-sg-list.png
new file mode 100644
index 0000000..5202b17
Binary files /dev/null and b/doc/content/aws/img/vpc-sg-list.png differ
diff --git a/doc/content/aws/img/vpc-subnet-create-private.png b/doc/content/aws/img/vpc-subnet-create-private.png
new file mode 100644
index 0000000..7e4c183
Binary files /dev/null and b/doc/content/aws/img/vpc-subnet-create-private.png differ
diff --git a/doc/content/aws/img/vpc-subnet-list.png b/doc/content/aws/img/vpc-subnet-list.png
new file mode 100644
index 0000000..aec781f
Binary files /dev/null and b/doc/content/aws/img/vpc-subnet-list.png differ
diff --git a/doc/content/aws/load-balancer.md b/doc/content/aws/load-balancer.md
new file mode 100644
index 0000000..a772a61
--- /dev/null
+++ b/doc/content/aws/load-balancer.md
@@ -0,0 +1,43 @@
+---
+title: "Load Balancer and Target Group"
+type: docs
+menu:
+ main:
+ name: 8. Load Balancer and Target Group
+ identifier: load_balancer
+ parent: aws
+ weight: 9
+---
+
+# Load Balancer and Target Group
+
+We'll need to create a load balancer to distribute traffic to your application server(s). If you use CloudFront and S3, you should use a load balancer - even if you only use 1 application server. We'll also create a target group, which tells the load balancer which servers to distribute traffic to.
+
+Go to **Services** -> **EC2**.
+
+1. Click **Load Balancers** on the left sidebar, and click **Create Load Balancer**.
+2. Click **Create** under **Application Load Balancer**.
+3. Under **Basic configuration** enter the following values:
+ * Load balancer name: `cc-load-balancer`
+ * Scheme: **Internet-facing**
+ * IP address type: **IPv4** or **Dualstack** (if you want to support IPv6)
+3. Under **Network Mappings** enter the following values:
+ * VPC: `Rustici CC VPC`
+ * Mappings: `us-east-1a` with the Subnet `CC Public 1` and `us-east-1b` with Subnet `CC Public 2`
+4. For the **Security groups** select the `CC Load Balancer`.
+5. For **Listeners and routing** configure the following listeners:
+ * **HTTPS:443**
+ * **HTTP:80**
+6. For these listeners, you will need to select **Create target group** located under **Default action** and configure the following values:.
+ * Target type: **Instances**
+ * Target group name: `cc-prod-target-group`
+ * Protocol and Port: **HTTP:80**
+ * VPC: `Rustici CC VPC`
+ * Protocol version: **HTTP1**
+ 8 Health check path: `/healthcheck`
+7. Under **Secure listener settings** you will need to configure the following:
+ * Choose a **Security policy**. See these [AWS SSL Security Policies](https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-policy-table.html) docs for more details about which one to pick, or take a look at the [FAQ Security docs](/self-hosting/faq/security).
+ * For **Default SSL/TLS certificate**, choose **From ACM** and select the certificate that you [requested or uploaded earlier](/self-hosting/aws/certificate-manager).
+16. Click **Create Load Balancer**.
+
+**Note:** You may want to enable deletion protection and access logs for your load balancer. For more information see [Access Logs for Elastic Load Balancers](https://aws.amazon.com/blogs/aws/access-logs-for-elastic-load-balancers/).
diff --git a/doc/content/aws/rds.md b/doc/content/aws/rds.md
new file mode 100644
index 0000000..ea60ff2
--- /dev/null
+++ b/doc/content/aws/rds.md
@@ -0,0 +1,77 @@
+---
+title: "Content Controller"
+type: docs
+menu:
+ main:
+ name: 7. RDS
+ identifier: rds
+ parent: aws
+ weight: 8
+---
+
+# RDS
+
+RDS (Relation Database Service) provides pre-configured and easy-to-use databases. Updates, backups, and fail-overs are managed by AWS. While you could setup your own EC2 instances running MySQL, we highly recommend using RDS.
+
+Content Controller works on MySQL 5.7 & 8.0 and the Aurora counterparts for those versions. We recommend MySQL 8.0, and that is the version we test against daily. Content Controller does not support read replicas (you can create one for your own usage, but the application won't take advantage of it).
+
+Go to **Services** -> **Relational Database Service**.
+
+## Subnet Groups
+
+Before launching your database, we'll need to create a Subnet Group to launch it in.
+
+1. Click on **Subnet groups** on the left sidebar and click the orange **Create DB Subnet Group** button.
+2. Enter `Rustici CC RDS Subnet Group` for the name, enter a description, and choose `Rustici CC VPC` for the VPC.
+3. Under Add Subnets, add both of your private subnets (no public subnets). Note: AWS doesn't show the subnet names here, so you may need to refer to the VPC Subnet list and take note of the IDs.
+
+## Parameter Groups
+
+A parameter group allows you to set certain DB parameters that will be applied when an instance is launched. We'll need a custom one.
+
+1. Click on **Parameter groups** on the left sidebar and click the orange **Create parameter group** button.
+2. Choose **mysql8.0** for the parameter group family, enter `Rustici-CC RDS-Paramter-Group` for the name, and enter a description, then click **Create**.
+3. Select the newly created parameter group from the list.
+4. Search for `log_bin_trust_function_creators`, check the box beside it, and click **Edit parameters**. Set it to `1`, and click **Save changes**.
+
+## Instances
+
+1. Click on **Databases** on the left sidebar and click the orange **Create database** button.
+2. Select **Standard create** and **MySQL**.
+3. Select the latest 8.0 version for **Engine Version**.
+4. Select **Production - MySQL**.
+5. Select **Multi-AZ DB instance**.
+6. Set the DB instance identifier to `cc-prod`.
+7. Set master username to `ccroot` or the value you chose for `mysql_root_user` in `group_vars/env.yml` earlier.
+8. Set master password to the value specified for `mysql_root_password` in `group_vars/env.yml`.
+9. For the DB instance class select at least a t4g.medium. Check out our recommendations in [Infrastructure Requirements](https://docs.contentcontroller.com/self-hosting/requirements/) or reach out to us if you have questions about size for your instance.
+10. For storage type choose `General Purpose (SSD)`.
+11. For allocated storage enter at least 100 GB [Read here for more information about IOPS vs storage size](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html) **Note that IOPS are the first limit people usually hit when scaling up with Content Controller
+12. Enter the following values under **Connectivity**.
+ * Virtual Private Cloud: `Rustici CC VPC`
+ * Subnet group: `rustici cc rds subnet group`
+ * Public accessibility: **No**
+ * Availability zone: **No preference**
+ * VPC security group: **Choose existing**, then remove **default**, and add `CC Database`
+ * Database port: 3306
+13. For database authentication select **Password authentication**.
+14. Configure the monitoring section to your preference.
+15. Enter the following values under **Additional Configuration** and click **Create database**.
+ * Database name: leave blank
+ * DB parameter group: `rustici-cc-rds-parameter-group`
+ * Option group: `default:mysql-8-0`
+ * Backup: Your preference - at least 1 day
+ * Encryption: Your preference
+ * Log exports: Your preference
+ * Maintenance: Your preference (but we recommend **Enable auto minor version upgrade** so that security patches are applied quickly)
+
+## Configuration
+
+Now that your database instance is up and running, you will need to setup the playbooks to point Content Controller at it.
+
+1. Select your new database from the **Instances** list in the **Relational Database Service** console.
+2. Scroll down to the **Connectivity & security** panel, and copy the **Endpoint**. (It should look something like `cc-prod.************.us-east-1.rds.amazonaws.com`).
+3. SSH to your Ansible instance and navigate to your `ContentController-PublicDeploy` folder.
+4. Edit `group_vars/content_controller.yml`.
+5. Find the line `cc_db_host: localhost` and replace `localhost` with the endpoint you copied from the RDS console.
+6. Save and exit.
diff --git a/doc/content/aws/route-53.md b/doc/content/aws/route-53.md
new file mode 100644
index 0000000..01d0c41
--- /dev/null
+++ b/doc/content/aws/route-53.md
@@ -0,0 +1,26 @@
+---
+title: "Route 53"
+type: docs
+menu:
+ main:
+ name: 2. Route 53
+ identifier: route_53
+ parent: aws
+ weight: 3
+---
+
+# Route 53
+
+Route 53 will manage the DNS for your Content Controller installation. You don't _have_ to use it, but it will make your life much easier. If you're like most organizations, you will probably want to just use a subdomain, and that's perfectly fine. We will use `demo.contentcontroller.net` for this example.
+
+Go to **Services** -> **Route 53**
+
+## Hosted Zone
+
+1. Click on **Hosted zones** on the left sidebar and click the blue **Create Hosted Zone** button.
+2. Enter your domain name here (we'll enter `demo.contentcontroller.net`, since we only want Route 53 to manage the `demo` subdomain), choose **Public Hosted Zone** as the type, and click **Create**.
+3. Now, you should see a list of record sets for your new hosted zone. Take note of your NS record. You will need to set these values at your registrar so that DNS can resolve.
+
+## Resources
+
+[What Is Amazon Route 53?](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html)
diff --git a/doc/content/aws/s3.md b/doc/content/aws/s3.md
new file mode 100644
index 0000000..e775aba
--- /dev/null
+++ b/doc/content/aws/s3.md
@@ -0,0 +1,64 @@
+---
+title: "S3"
+type: docs
+menu:
+ main:
+ name: 6. S3
+ identifier: s3
+ parent: aws
+ weight: 7
+---
+
+# S3
+
+Simple Storage Service (S3) provides a cost-effective and flexible place to store your content. If you're using more than one server inside of AWS, we recommend using a combination of S3 and CloudFront for serving your content.
+
+## CloudFront Origin Access Identity
+
+To provision S3, the necessary IAM users, etc, we first need to create an Origin Access Identity for CloudFront.
+
+1. Make sure you are logged in to the AWS console as the root user (IAM users will not work).
+2. Go here https://console.aws.amazon.com/cloudfront/home?region=us-east-1#oai: and click **Create OAI**.
+3. Enter `Rustici Content Controller OAI` for the comment and click **Create**.
{{< img src="/self-hosting/aws/img/cf-oai-comment.png" >}}
+4. Take note of the ID beside `Rustici Content Controller OAI`. You will need it in a few steps.
+
+## S3 Bucket and IAM User Setup
+
+This part has quite a few steps, so we've provided an Ansible Playbook to setup your S3 buckets, logging, and IAM user "automagically". This playbook will:
+
+* Create 2 new S3 buckets for you (one for content and one for logs).
+* Set up an IAM user, group, and policy that assign sufficient rights to read/write from the new bucket.
+* Grant your CloudFront Origin Access Identity the rights it needs to access the bucket.
+* Create a valid `group_vars/s3.yml` file for Ansible to use when it sets up Content Controller.
+
+### Run the Playbook
+
+1. SSH in to your Ansible instance, and go to your `ContentController-PublicDeploy` folder.
+2. Run the following command (but replace `CLOUDFRONT_ORIGIN_ACCESS_IDENTITY_ID` and `DEMO.CONTENTCONTROLLER.NET` with the proper values for your install).
+```
+ansible-playbook aws-s3.yml \
+ -e "cloudfront_origin_access_identity=CLOUDFRONT_ORIGIN_ACCESS_IDENTITY_ID" \
+ -e "ServerName=DEMO.CONTENTCONTROLLER.NET" \
+ -e "env=prod" \
+ -e "ClientName=Rustici"
+```
+3. Enter the following values when asked (but replace `CLIENT_NAME` with the name of your company with no spaces). If you plan on setting up a separate staging environment, add `stg` or `prod` to your bucket name prefix and username.
+ 1. S3 bucket name prefix: `CLIENT_NAME`
+ 2. AWS account ID (you can find this in the AWS console)
+ 3. Username: `rustici-cc-s3-user`
+4. Everything you need for S3 to work should have been created. A `group_vars/s3.yml` file has been created with the credentials needed for Content Controller to access S3.
+
+## Deleting It All and Starting Over
+
+Sometimes things don't work on the first try, and you need to start over. To undo all of your changes and delete the bucket and user you've created, run the playbook again with the `Slartibartfast` variable set to true.
+
+NOTE THAT THIS IS DANGEROUS AND WILL DESTROY YOUR S3 BUCKET AND EVERYTHING IN IT. ONLY USE IT IF YOU HAVEN'T STARTED USING S3 IN PRODUCTION.
+
+```
+ansible-playbook aws-s3.yml \
+ -e "cloudfront_origin_access_identity=CLOUDFRONT_ORIGIN_ACCESS_IDENTITY_ID" \
+ -e "ServerName=DEMO.CONTENTCONTROLLER.NET" \
+ -e "env=prod" \
+ -e "ClientName=Rustici" \
+ -e '{"Slartibartfast": true}'
+```
diff --git a/doc/content/aws/vpc.md b/doc/content/aws/vpc.md
new file mode 100644
index 0000000..c37a845
--- /dev/null
+++ b/doc/content/aws/vpc.md
@@ -0,0 +1,183 @@
+---
+title: "VPC"
+type: docs
+menu:
+ main:
+ name: 1. VPC Setup
+ identifier: vpc_setup
+ parent: aws
+ weight: 2
+---
+
+# VPC
+
+A VPC (virtual private cloud) is a logically isolated section of the cloud that allows you to define your own virtual network (similar to having your own router and switches on premise). We will take advantage of VPC to create our own public and private subnets, which will contain our servers, database, load balancer, etc.
+
+For this example VPC, I picked a Class A IP address pool of `10.42.0.0/16`. You can pick any Class A or Class B `/16` subnet that you wish. However, if you plan to setup a VPN into your VPC, make sure the pool doesn't conflict with an IP addresses on your other networks.
+
+Below is a diagram of the network we will define.
+
+{{< img src="/self-hosting/aws/img/vpc-diagram.png" >}}
+
+Go to **Services** -> **VPC**
+
+1. Choose your preferred region from the top right corner of the screen. We're going to choose **US East (N. Virginia)**, so all of our availability zone settings will be prefixed with 'us-east-1'. See the [AWS Regions and Availability Zones docs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html) for more details. Keep in mind that pricing varies between regions.
+2. Choose **Your VPCs** on the left sidebar, and then click the blue **Create VPC** button.
+3. Enter a name and IPv4 CIDR block and click **Yes, Create**. If you aren't sure what to enter here, use `Rustici CC VPC` and `10.42.0.0/16`.
+4. Your VPC should now appear in the list of VPCs. Select it, click **Actions** -> **Enable DNS Hostnames**. Choose **Yes** and click **Save**.
+
+## Subnets
+
+Next, we'll create subnets for your new VPC. You may do this however you like, but you'll need at least 2 public subnets in different availability zones and 2 private subnets in different availability zones. To keep things simple, we're going to create 2 public subnets in zone a and zone b and 2 private subnets in zone a and zone b.
+
+1. Select **Subnets** on the left sidebar, and then click the blue **Create Subnet** button.
+2. Select the VPC that you created earlier (such as `Rustici CC VPC`), fill out the rest of the details, and click **Yes, Create**. If you aren't sure what to enter here, create 4 subnets with the following values:
+
+| Name tag | Availability zone | IPv4 CIDR block |
+| ------------- | ----------------- | --------------- |
+| CC Private 1 | us-east-1a | 10.42.0.0/19 |
+| CC Private 2 | us-east-1b | 10.42.32.0/19 |
+| CC Public 1 | us-east-1a | 10.42.64.0/19 |
+| CC Public 2 | us-east-1b | 10.42.96.0/19 |
+
+{{< img src="/self-hosting/aws/img/vpc-subnet-create-private.png" >}}
+
+The important thing here is that you pick 2 availability zones, and you create 1 public and 1 private subnet in each of the 2 zones. We used `/19` subnets, so you will have room for up to 4 more `/19` subnets in your `/16` in the future.
+
+You should now see a list containing the 4 subnets you just created.
+
+{{< img src="/self-hosting/aws/img/vpc-subnet-list.png" >}}
+
+## Internet Gateways
+
+Now, we need to create an Internet Gateway for your VPC.
+
+1. Select **Internet Gateways** on the left sidebar, and then click the blue **Create internet gateway** button.
+2. Name the gateway and click **Create**. If you aren't sure what to enter here, use `Rustici CC VPC IGW`.
+3. You will be taken back to the list of gateways. Check the box beside the gateway that you just created, and click **Actions** -> **Attach to VPC**. Select the VPC that you created earlier (`Rustici CC VPC` or the name you chose), and then click **Attach**.
+
+{{< img src="/self-hosting/aws/img/vpc-igw-attach.png" >}}
+
+## NAT Gateways
+
+Since our private subnet instances will not have public IP addresses, their traffic will have to pass through a NAT gateway in order to access the internet. The NAT gateways will go in the public subnets, and outbound traffic from the private subnets will be routed to those NAT instances.
+
+1. Select **NAT Gateways** on the left sidebar, and then click the blue **Create NAT Gateway** button.
+2. Choose your first public subnet (`CC Public 1`).
+3. Click **Create New EIP** (This is the public IP address that outbound traffic will masquerade as).
+4. Click **Create a NAT Gateway**.
+5. Click **Close** (We'll edit the route tables later).
+6. Rename the NAT gateway you just created to `CC Private 1 NAT`.
+7. Repeat these steps again to create a second NAT gateway, but this time choose your second public subnet (`CC Public 2`) and name it `CC Private 2 NAT`.
+
+## Route Tables
+
+Currently, all of our subnets have the same route table. This won't work, because we need the private subnet traffic to flow through a NAT gateway, since those instances won't have a public IP address. To fix this, we'll create 2 new route tables (1 for each private subnet).
+
+### Public Route Table
+
+The public route table will keep all local VPC traffic inside the VPC, and it will direct all outgoing traffic to the internet gateway. When your VPC was created, it also created a default route table. We'll start out by editing that one.
+
+1. Select **Route Tables** on the left sidebar. Find the route table associated with your VPC (It will say `Rustici CC VPC` or the name you chose under the VPC column) and change its name to `CC Public Routes` .
+2. Select the route table. At the bottom of the screen, a panel will appear with some tabs. The **Routes** tab should be selected. Click the **Edit** button.
+3. Click **Add Another Route**.
+4. In the boxes that appear, set `0.0.0.0/0` for the destination and choose `Rustici CC VPC IGW` for the target, and click **Save**.
+5. Click on the **Subnet Association** tab and click **Edit**.
+6. Check the boxes beside both both public subnets and click **Save**.
{{< img src="/self-hosting/aws/img/vpc-rtb-public-subnet-assc.png" >}}
+
+### Private Route Table
+
+The private route table will keep all local VPC traffic inside the VPC, and it will direct all outgoing traffic to the NAT instance located in the public subnet. We'll create 2 route tables so that each private subnet directs traffic to the NAT instance in the public subnet in the corresponding availability zone.
+
+1. Select **Route Tables** on the left sidebar and click **Create Route Table**.
+2. Enter `CC Private 1 Routes` for the name tag, choose `Rustici CC VPC` for the VPC, and click **Yes, Create**.
+3. The new route table should be selected, if it isn't, then go ahead and select it. Select the **Routes** tab and click **Edit**.
+4. Click **Add Another Route**.
+5. In the boxes that appear, set `0.0.0.0/0` for the destination and choose the `CC Private 1 NAT` for the target, and click **Save**. Note: AWS does not show the Name for NAT gateways, so you'll need to open the **NAT Gateways** tab on the side and take note of the IDs.
+6. Click on the **Subnet Association** tab and click **Edit**.
+7. Check the box beside only the `CC Private 1` subnet, and click **Save**.
+8. Repeat steps 1-7, but this time use `CC Private 2` and `CC Private 2 NAT` instead.
+
+## Security Groups
+
+We'll create some security groups to make sure only traffic we want is allowed to flow. The goal here is to keep the potential attack surface as small as possible, so the only traffic that will be allowed from the outside world is HTTPS to the load balancers and SSH to the Ansible control box.
+
+### Create security groups
+
+1. Select **Security Groups** on the left sidebar.
+2. For each of these rows click **Create Security Group**, enter the values, and click **Yes, Create**.
+
+| Name tag | Group name | Description | VPC |
+| ---------------- | ---------------- | ------------------------------------ | -------------- |
+| CC Database | CC Database | Security group for CC databases | Rustici CC VPC |
+| CC Application | CC Application | Security group for CC app servers | Rustici CC VPC |
+| CC Ansible | CC Ansible | Security group for CC Ansible server | Rustici CC VPC |
+| CC Load Balancer | CC Load Balancer | Security group for CC load balancer | Rustici CC VPC |
+
+You should now have 5 security groups (1 default and the 4 we just created) for the `Rustici CC VPC`
+
+{{< img src="/self-hosting/aws/img/vpc-sg-list.png" >}}
+
+#### CC Database
+
+Add rules to allow incoming DB traffic from `CC Application` and `CC Ansible`.
+
+1. Select the `CC Database` security group from the list.
+2. Click the **Inbound Rules** tab and click **Edit**.
+3. Choose **MySQL/Aurora (3306)** for the type, choose `CC Application` for the source, and enter `CC app server DB traffic` for the description.
+4. Click **Add another rule**
+5. Choose **MySQL/Aurora (3306)** for the type, choose `CC Ansible` for the source, and enter `Ansible server DB traffic` for the description.
+6. Click **Save**
+
+{{< img src="/self-hosting/aws/img/vpc-sg-inbound-db.png" >}}
+
+#### CC Application
+
+Add rules to allow incoming HTTP traffic from `CC Load Balancer` and all incoming traffic from `CC Ansible`. Add a rule to allow outgoing DB traffic to `CC Database`.
+
+1. Select the `CC Application` security group from the list.
+2. Click the **Inbound Rules** tab and click **Edit**.
+3. Choose **HTTP (80)** for the type, choose `CC Load Balancer` for the source, and enter `Load balancer HTTP traffic` for the description.
+4. Click **Add another rule**.
+5. Choose **ALL Traffic** for the type, choose `CC Ansible` for the source, and enter `Ansible traffic` for the description.
+6. Click **Save**.
+7. Click the **Outbound Rules** tab, and click **Edit**.
+8. Click **Add another rule**.
+9. Choose **MySQL/Aurora (3306)** for the type, choose `CC Database` for the destination, and enter `CC app to DB traffic` for the description.
+10. Click **Save**.
+
+#### CC Load Balancer
+
+Add rules to allow all incoming traffic ([per AWS docs](https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-groups.html)). Add a rule to allow outgoing HTTP traffic to `CC Application`.
+
+1. Select the `CC Load Balancer` security group from the list.
+2. Click the **Inbound Rules** tab and click **Edit**.
+3. Choose **ALL Traffic** for the type and enter `0.0.0.0/0` for the source.
+4. Click **Save**.
+5. Click the **Outbound Rules** tab, and click **Edit**.
+6. **Remove** all existing rules, and then click **Add another rule**.
+7. Choose **HTTP (80)** for the type, choose `CC Application` for the destination, and enter `Load balancer to CC app traffic` for the description.
+8. Click **Save**.
+
+#### CC Ansible
+
+Add a rule to allow incoming SSH traffic. Add rules to allow outdoing DB traffic to `CC Database` and all outgoing traffic to `CC Application`.
+
+1. Select the `CC Load Balancer` security group from the list.
+2. Click the **Inbound Rules** tab and click **Edit**.
+3. Choose **SSH (22)** for the type, enter `0.0.0.0/0` for the source, and enter `SSH` for the description. _If possible, restrict this to only the IP address for your machine or the IP address range for your company's network._
+4. Click **Save**.
+5. Click the **Outbound Rules** tab, and click **Edit**.
+6. Click **Add another rule**.
+7. Choose **ALL Traffic** for the type, choose `CC Application` for the destination, and enter `Ansible to CC app traffic` for the description.
+8. Click **Add another rule**.
+9. Choose **MySQL/Aurora (3306)** for the type, choose `CC Database` for the destination, and enter `Ansible to CC DB traffic` for the description.
+10. Click **Save**.
+
+## Resources
+
+[What Is Amazon VPC?](https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Introduction.html)
+
+[AWS Fault Tolerance & High Availability](https://media.amazonwebservices.com/architecturecenter/AWS_ac_ra_ftha_04.pdf)
+
+[AWS VPCs and Subnets](https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html)
diff --git a/doc/content/deploy-tools.md b/doc/content/deploy-tools.md
new file mode 100644
index 0000000..157ae79
--- /dev/null
+++ b/doc/content/deploy-tools.md
@@ -0,0 +1,171 @@
+---
+title: "Deploy Tools Reference"
+type: docs
+menu: "main"
+weight: 3
+---
+# Deploy Tools Reference
+
+Content Controller is a fairly complex application, so we've created a set of Ansible playbooks and Bash scripts to help you deploy it.
+
+[Ansible playbooks](http://docs.ansible.com/ansible/2.4/playbooks.html) are a collection of tasks that describe how application componets are deployed. These playbooks are further broken down in to [Roles](http://docs.ansible.com/ansible/2.4/playbooks_reuse_roles.html). There is usually one role per application component. You'll want to pay special attention to the section labeled **Group Vars** below, as that is how you will configure your installation.
+
+To make it easier to understand, let's dive in to the Content Controller playbooks. [You can find those here on GitHub](https://github.com/RusticiSoftware/ContentController-PublicDeploy).
+
+Note that you don't *have* to understand every detail of these playbooks, but if you are hosting the application yourself, it certainly helps when things go sideways to have an idea of what is running on your servers and how it gets there.
+
+## Bash Scripts
+
+There are a few bash scripts that are useful for getting started.
+
+### `bootstrap.sh`
+
+Run this on your Ansible control server. It will install all of the necessary dependencies for running the playbooks.
+
+### `setup.sh`
+
+Only run this once! It sets up the initial group vars for you, sets sane defaults, and generates passwords and secrets. After adding your `keypair.yml` file, run this script.
+
+## Roles
+
+Inside the `roles` folder, you'll find several folders. Each folder is a role which contains default variables, tasks, files, and templates and is responsibile for installing and configuring an application component.
+
+* `tasks` describe exactly what the role will do and when.
+* `defaults` are variables that can be configured by you if needed. If you want to change one of these variables, place those changes somewhere in your `group_vars` or `host_vars` - don't modify the actual files in `defaults`.
+* `vars` are variables that are used by the role. They typically shouldn't be changed (overwriting them in the `group_vars` or `host_vars` won't work).
+* `handlers` are executed by the `tasks` when things are changed. We typically use them to restart services after the role has finished executing.
+
+### `apache`
+
+Installs the Apache web server, which acts as a reverse proxy to Content Controller and SCORM Engine.
+
+### `aws-s3`
+
+Provisions S3 buckets, IAM users, and creates an S3 group var when initial provisioning Content Controller inside of Amazon Web Services. This role is covered in more detail in the AWS setup. It will only be used one time.
+
+### `cc-scorm-engine`
+
+Installs Rustici Engine, which carries the bulk of the load when serving courses to learners.
+
+### `cloudfront`
+
+Copies cookie signing keys for AWS CloudFront to the proper locations on the server.
+
+### `common`
+
+Performs some important tasks such as installing operating system dependencies and setting the server timezone to UTC.
+
+### `content-controller`
+
+Installs the Content Controller application and writes configuration templates.
+
+### `java`
+
+Installs and updates the Oracle Java JDK to the latest supported version.
+
+### `mnt`
+
+Sets up folders and permissions for temp files or mounted folders when using a storage solution other than AWS S3.
+
+### `mysql-config`
+
+Installs local dependencies for connecting to MySQL, creates application DB users with appropriate permissions, sets up tables, and performs some hardening.
+
+### `mysql-local`
+
+Installs a local copy of MySQL for QA and staging environments.
+
+### `ssl`
+
+Installs SSL certs when terminating SSL at Apache. Also generates self-signed SSL certs for QA and staging environments.
+
+### `tomcat`
+
+Installs and configures Apache Tomcat for use by Rustici Engine.
+
+## Group Vars
+
+### `aws.yml`
+
+Contains settings for the `build_ami.yml` playbook. If you are not using AWS or you don't want to deploy releases using the AMI builder, then you can ignore this file.
+
+### `cloudfront.yml`
+
+Enables AWS CloudFront and provides settings for allowing Content Controller to sign CloudFront cookies. You can ignore this if you aren't using CloudFront and S3.
+
+### `content_controller.yml`
+
+Contains important settings for the Content Controller application such as database credentials, auth token secret key, initial application user credentials, and email credentials.
+
+### `engine_java.yml`
+
+Contains settings for Rustici Engine. You can probably ignore most of these.
+
+### `env.yml`
+
+This one is where the majority of the configuration happens, and you should review it carefully. Some important things to check on in this file include:
+* `ServerName` - the domain name used by Content Controller
+* `S3FileStorageEnabled` - you'll want to set this to true if you're using AWS S3
+* SSL settings - we recommend terminating SSL at the load balancer if you're using AWS. If not, you'll want to follow the instructions for adding SSL certificates and set `use_ssl` to true.
+* Heap size - if your application servers have more than 4 GB RAM, you'll want to adjust these values.
+
+### `keypair.yml`
+
+This file contains credentials for downloading the latest release of Content Controller. It will be provided by Rustici during the project kickoff.
+
+### `s3.yml`
+
+Contains settings for AWS S3. If you run the `aws-s3.yml` playbook to setup S3, then this file will be created for you.
+
+## Host Vars
+
+If you have a staging and production installation of CC and want specific settings to vary between them, using `host_vars` will be how you accomplish that. You will need to add a separate `host_vars` file for each environment that contains the different settings. These files will overwrite settings from the shared `group_vars` config files. An example setup might look like:
+
+* `host_vars/demo.contentcontroller.net.yml`
+* `host_vars/demo-staging.contentcontroller.net.yml`
+
+All of your `group_vars` files will stay the same, and the values that the settings should have for production will be specified in your `host_vars` entry. There may be other settings you want to differ between environments, but we expect the following to be set:
+
+* `ServerName`: the domain name used by Content Controller
+* `cc_db_host`: used to ensure that your deployment doesn't affect the test environment's database
+* `mysql_root_password`: the root password of the target database
+
+We also recommend changing the following between environments for the sake of security, but it isn't necessary. The exact values don't matter, as long as they are unique and secure passwords:
+
+* `cc_db_password`
+* `engine_db_password`
+* `engine_password`
+* `tomcat_password`
+* `secret_key`
+* `cc_user_password`
+
+When you want to reference a `host_vars` file during deployment, just add the argument `--extra-vars="@host_vars/name.of.file.yml"` somewhere before the `env.yml`. Ansible will read the file you've provided, and the settings specified in that file will overwrite the values you've configured in the `group_vars` directory.
+
+Again, these files are for environment-specific settings. Anything that applies to both staging and production environments (like SMTP settings, file paths, etc) will go in the `group_vars` directory, and anything environment-specific goes in the `host_vars` directory for that environment. If you have any questions about the best way to setup a separate staging and production environment, give us a call.
+
+## Playbooks
+
+We provide several playbooks to assist you with deploying Content Controller.
+
+### `aws-s3.yml`
+
+This playbook sets up the majority of the necessary bits in AWS for using S3 with Content Controller.
+
+### `build-ami.yml`
+
+If you're using AWS, this is the best way to deploy Content Controller. It creates a new EC2 instance, deploys Content Controller to it, creates an AMI (Amazon Machine Image), and then destroys the EC2 instance. You can then add the AMI to an autoscaling group or use the image to spin up a few servers and add them to your load balancer target. Refer to the deploying on AWS section for more information.
+
+### `env.yml`
+
+Deploys Content Controller. It will automatically load the group vars.
+
+### `env-novars.yml`
+
+Also deploys Content Controller, but skips loading group vars. You must include the group vars from the command line or provide a fully-loaded host vars file. You probably won't need this one.
+
+## Helpful Resources
+
+* [Ansible Documentation](http://docs.ansible.com/ansible/2.4/index.html)
+* [Ansible Playbooks](http://docs.ansible.com/ansible/2.4/playbooks.html)
+* [Ansible Roles](http://docs.ansible.com/ansible/2.4/playbooks_reuse_roles.html)
+* [Where Should I Put A Variable?](http://docs.ansible.com/ansible/2.4/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)
diff --git a/doc/content/faq/configure-email.md b/doc/content/faq/configure-email.md
new file mode 100644
index 0000000..9ebd7e0
--- /dev/null
+++ b/doc/content/faq/configure-email.md
@@ -0,0 +1,29 @@
+---
+title: "Configuring Email"
+type: docs
+menu:
+ main:
+ name: Configuring Email
+ identifier: configuring_email
+ parent: faq
+ weight: 1
+---
+
+# Configuring Email
+
+Content Controller has the ability to send emails for certain events such as account licenses nearing their limits or password resets. To enable this support, you'll need to use some sort of SMTP service. You can use AWS SES, SendGrid, a Gmail account, or any other service that supports SMTP. We've had pretty good luck with SendGrid.
+
+To configure email:
+
+1. Connect to your Ansible control server and navigate to the `ContentController-PublicDeploy` folder.
+2. Edit `group_vars/content_controller.yml` and change `enable_emails: false` to `enable_emails: true`.
+3. Fill in the rest of the relevant options for your email provider and save it.
+4. Deploy Content Controller (see the [Installation Guide](/self-hosting/quick-start)).
+
+## Resources
+
+[License Alert Emails in Content Controller](https://support.scorm.com/hc/en-us/articles/115003101773-License-Alert-Emails)
+
+[Sending Email with Amazon SES](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-email.html)
+
+[Sending Email with SendGrid](https://sendgrid.com/solutions/email-api/)
diff --git a/doc/content/faq/enable-http.md b/doc/content/faq/enable-http.md
new file mode 100644
index 0000000..c2fba0a
--- /dev/null
+++ b/doc/content/faq/enable-http.md
@@ -0,0 +1,59 @@
+---
+title: "Enabling Support for HTTP Learners"
+type: docs
+menu:
+ main:
+ name: Enabling HTTP Learners
+ identifier: http
+ parent: faq
+ weight: 3
+---
+
+# Enabling Support for HTTP Learners
+
+Some LMSs do not support launching courses via HTTPS. When possible, we recommend always using HTTPS (which is why HTTP support is disabled by default). If you find that you need to enable support for launching courses via HTTP, follow these instructions.
+
+## AWS
+
+If you are not using AWS, you'll need to update your load balancer and firewall to allow HTTP traffic (TCP/80) to your application servers. If you are using AWS, you'll need to make sure you allow HTTP access on your load balancer and in CloudFront.
+
+**Note:** By default, we will still redirect users of the management UI to HTTPS (based on the `X-Forwarded-Proto` from your load balancer), even if HTTP is enabled.
+
+### Enabling HTTP Access on the Application Load Balancer
+
+1. Go to **Services** -> **EC2**.
+2. Click on **Load Balancers** on the left side bar.
+3. Select your CC load balancer and click on the **Listeners** tab.
+4. Click **Add listener**. Choose **HTTP** for the protocol, `80` for the port, and your app server's target group.
+5. Click **Create**.
+
+{{< img src="/self-hosting/faq/img/ec2-elb-listeners.png" >}}
+
+### Enabling HTTP Access on the Load Balancer Security Group
+
+1. Go to **Services** -> **EC2**.
+2. Click on **Security Groups** on the left side bar.
+3. Select your CC Load Balancer security group and click on the **Inbound** tab.
+4. Click **Edit** and add a rule for **HTTP** from `0.0.0.0/0`.
+5. Click **Save**.
+
+{{< img src="/self-hosting/faq/img/ec2-sg-elb-inbound-rules.png" >}}
+
+### Enabling HTTP Access on the CloudFront Distribution
+
+1. Go to **Services** > **CloudFront.
+2. Choose your CC CloudFront distribution, and then click on the **Origins** tab.
+3. Select the origin for your load balancer, and click **Edit**.
+4. Change Origin Protocol Policy to **Match Viewer**, and click **Yes, Edit**.
+5. Click on the **Behaviors** tab, select your S3 behavior, and click **Edit**.
+6. Change the Viewer Protocol Policy to **HTTP and HTTPS**, and click **Yes, Edit**.
+7. Click on the **Behaviors** tab, select your load balancer behavior, and click **Edit**.
+8. Change the Viewer Protocol Policy to **HTTP and HTTPS**, and click **Yes, Edit**.
+
+It may take a few minutes for these CloudFront changes to propagate.
+
+## Application Config
+
+1. Connect to your Ansible control server and navigate to the `ContentController-PublicDeploy` folder.
+2. Edit `group_vars/env.yml` and change `allow_80: false` to `allow_80: true`. If it is already true, then you're good to go (no need to re-deploy).
+3. Deploy Content Controller (see the [Installation Guide](/self-hosting/quick-start)).
diff --git a/doc/content/faq/img/ec2-elb-listeners.png b/doc/content/faq/img/ec2-elb-listeners.png
new file mode 100644
index 0000000..193309b
Binary files /dev/null and b/doc/content/faq/img/ec2-elb-listeners.png differ
diff --git a/doc/content/faq/img/ec2-sg-elb-inbound-rules.png b/doc/content/faq/img/ec2-sg-elb-inbound-rules.png
new file mode 100644
index 0000000..bc2dd34
Binary files /dev/null and b/doc/content/faq/img/ec2-sg-elb-inbound-rules.png differ
diff --git a/doc/content/faq/licenses.md b/doc/content/faq/licenses.md
new file mode 100644
index 0000000..4a54153
--- /dev/null
+++ b/doc/content/faq/licenses.md
@@ -0,0 +1,284 @@
+---
+title: "Third-Party Licenses"
+type: docs
+menu:
+ main:
+ name: Third-Party Licenses
+ identifier: licenses
+ parent: faq
+ weight: 6
+---
+
+# Rustici Software Content Controller Third Party Licenses
+
+Content Controller is distributed with a number of freely redistributable libraries under various licenses. A summary of those licenses are included below including the relevant license of that library.
+
+## Server-side dependencies
+
+| Library | License |
+| ---- | ---- |
+| [antlr:antlr](http://www.antlr.org/) | [BSD License](http://www.antlr.org/license.html) |
+| [antlr:stringtemplate](http://www.stringtemplate.org/) | [The BSD 3-Clause License](http://opensource.org/licenses/BSD-3-Clause) |
+| [ch.qos.logback:logback-access](http://www.qos.ch) | [LGPL v2.1](https://www.gnu.org/licenses/lgpl-2.1) |
+| [ch.qos.logback:logback-classic](http://www.qos.ch) | [LGPL v2.1](https://www.gnu.org/licenses/lgpl-2.1) |
+| [ch.qos.logback:logback-core](http://www.qos.ch) | [LGPL v2.1](https://www.gnu.org/licenses/lgpl-2.1) |
+| [com.auth0:java-jwt](https://github.com/auth0/java-jwt) | [The MIT License (MIT)](https://raw.githubusercontent.com/auth0/java-jwt/master/LICENSE) |
+| [com.auth0:jwks-rsa](https://github.com/auth0/jwks-rsa-java) | [The MIT License (MIT)](https://raw.githubusercontent.com/auth0/jwks-rsa-java/master/LICENSE) |
+| [com.beust:jcommander](http://beust.com/jcommander) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.datadoghq:java-dogstatsd-client](https://github.com/DataDog/java-dogstatsd-client) | [MIT License](https://opensource.org/licenses/MIT) |
+| [com.fasterxml.jackson.core:jackson-annotations](http://github.com/FasterXML/jackson) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.core:jackson-core](https://github.com/FasterXML/jackson-core) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.core:jackson-databind](http://github.com/FasterXML/jackson) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.dataformat:jackson-dataformat-csv](https://github.com/FasterXML/jackson-dataformats-text) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.dataformat:jackson-dataformat-yaml](https://github.com/FasterXML/jackson-dataformats-text) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.datatype:jackson-datatype-guava](https://github.com/FasterXML/jackson-datatypes-collections) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.datatype:jackson-datatype-jdk8](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.datatype:jackson-datatype-joda](http://wiki.fasterxml.com/JacksonModuleJoda) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.datatype:jackson-datatype-jsr310](https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.jaxrs:jackson-jaxrs-base](http://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-base) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider](http://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-json-provider) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.module:jackson-module-afterburner](https://github.com/FasterXML/jackson-modules-base) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.module:jackson-module-jaxb-annotations](https://github.com/FasterXML/jackson-modules-base) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.jackson.module:jackson-module-parameter-names](https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml.woodstox:woodstox-core](https://github.com/FasterXML/woodstox) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.fasterxml:classmate](http://github.com/cowtowncoder/java-classmate) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.google.code.findbugs:jsr305](http://findbugs.sourceforge.net/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.google.errorprone:error_prone_annotations](http://errorprone.info/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.google.guava:failureaccess](https://github.com/google/guava/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.google.guava:guava](https://github.com/google/guava/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| com.google.guava:listenablefuture | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.google.j2objc:j2objc-annotations](https://github.com/google/j2objc/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer](http://code.google.com/p/owasp-java-html-sanitizer) | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) |
+| [com.helger:profiler](https://github.com/phax/profiler) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.jamesmurty.utils:java-xmlbuilder](https://github.com/jmurty/java-xmlbuilder) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.rusticisoftware:tincan](http://rusticisoftware.github.io/TinCanJava/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.sun.istack:istack-commons-runtime](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [com.sun.mail:javax.mail](http://www.oracle.com) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [com.sun.xml.fastinfoset:FastInfoset](http://fi.java.net) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.webcohesion.enunciate:enunciate-core-annotations](http://enunciate.webcohesion.com/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [com.zaxxer:HikariCP](https://github.com/brettwooldridge/HikariCP) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [commons-codec:commons-codec](https://commons.apache.org/proper/commons-codec/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [commons-codec:commons-codec](http://commons.apache.org/proper/commons-codec/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [commons-collections:commons-collections](http://commons.apache.org/collections/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [commons-fileupload:commons-fileupload](http://commons.apache.org/proper/commons-fileupload/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [commons-io:commons-io](http://commons.apache.org/proper/commons-io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [commons-lang:commons-lang](http://commons.apache.org/lang/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [commons-logging:commons-logging](http://commons.apache.org/proper/commons-logging/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [de.svenkubiak:jBCrypt](http://www.mindrot.org/projects/jBCrypt) | [ISC/BSD licence](http://www.mindrot.org/files/jBCrypt/LICENSE) |
+| [io.dropwizard.metrics:metrics-annotation](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-core](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-healthchecks](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-httpclient](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-jdbi](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-jersey2](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-jetty9](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-jmx](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-json](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-jvm](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-logback](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.metrics:metrics-servlets](http://metrics.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard.modules:dropwizard-flyway](https://github.com/dropwizard/dropwizard-flyway) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-assets](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-auth](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-client](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-configuration](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-core](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-db](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-forms](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-jackson](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-jdbi](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-jersey](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-jetty](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-lifecycle](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-logging](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-metrics](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-request-logging](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-servlets](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-util](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-validation](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-views](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [io.dropwizard:dropwizard-views-freemarker](http://www.dropwizard.io/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| io.swagger:swagger-annotations | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| io.swagger:swagger-core | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| io.swagger:swagger-jaxrs | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| io.swagger:swagger-jersey2-jaxrs | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| io.swagger:swagger-models | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [javax.activation:activation](http://java.sun.com/javase/technologies/desktop/javabeans/jaf/index.jsp) | [CDDL v1.0](https://opensource.org/licenses/cddl1) |
+| [javax.activation:javax.activation-api](http://www.oracle.com) | [CDDL/GPLv2+CE](https://github.com/javaee/activation/blob/master/LICENSE.txt) |
+| [javax.annotation:javax.annotation-api](http://jcp.org/en/jsr/detail?id=250) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [javax.inject:javax.inject](http://code.google.com/p/atinject/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [javax.json:javax.json-api](http://json-processing-spec.java.net) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [javax.servlet:javax.servlet-api](http://servlet-spec.java.net) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [javax.validation:validation-api](http://beanvalidation.org) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [javax.ws.rs:javax.ws.rs-api](http://jax-rs-spec.java.net) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [javax.xml.bind:jaxb-api](http://www.oracle.com/) | [CDDL 1.1](https://oss.oracle.com/licenses/CDDL+GPL-1.1) |
+| [jmurty:jets3t](https://bitbucket.org/jmurty/jets3t/overview) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [joda-time:joda-time](https://www.joda.org/joda-time/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [junit:junit](http://junit.org) | [Eclipse Public License 1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [net.iharder:base64](http://iharder.net/base64/) | Public domain |
+| net.oauth.core:oauth | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| net.oauth.core:oauth-provider | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| net.shibboleth.tool:xmlsectool | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| net.shibboleth.utilities:java-support | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [net.sourceforge.argparse4j:argparse4j](http://argparse4j.github.io) | [MIT License](https://opensource.org/licenses/MIT) |
+| [net.sourceforge.cssparser:cssparser](http://cssparser.sourceforge.net/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [net.spy:spymemcached](http://www.couchbase.org/code/couchbase/java) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.commons:commons-lang3](https://commons.apache.org/proper/commons-lang/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.commons:commons-lang3](http://commons.apache.org/proper/commons-lang/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.commons:commons-text](https://commons.apache.org/proper/commons-text) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.commons:commons-text](http://commons.apache.org/proper/commons-text/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.httpcomponents:fluent-hc](http://hc.apache.org/httpcomponents-client) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.httpcomponents:httpclient](http://hc.apache.org/httpcomponents-client) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.httpcomponents:httpcore](http://hc.apache.org/httpcomponents-core-ga) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.santuario:xmlsec](https://santuario.apache.org/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.tika:tika-core](http://tika.apache.org/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.tomcat:tomcat-jdbc](https://tomcat.apache.org/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.tomcat:tomcat-juli](https://tomcat.apache.org/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.apache.velocity:velocity](http://velocity.apache.org/engine/devel/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.bouncycastle:bcprov-jdk15on](http://www.bouncycastle.org/java.html) | [MIT License](https://opensource.org/licenses/MIT) |
+| [org.checkerframework:checker-compat-qual](https://checkerframework.org) | [MIT License](https://opensource.org/licenses/MIT) |
+| [org.checkerframework:checker-qual](https://checkerframework.org) | [MIT License](https://opensource.org/licenses/MIT) |
+| [org.codehaus.mojo:animal-sniffer-annotations](http://www.mojohaus.org/animal-sniffer/animal-sniffer-annotations/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.codehaus.woodstox:stax2-api](http://wiki.fasterxml.com/WoodstoxStax2) | [The BSD License](http://www.opensource.org/licenses/bsd-license.php) |
+| [org.coursera:dropwizard-metrics-datadog](https://github.com/coursera/metrics-datadog/) | [The BSD 3-Clause License](http://opensource.org/licenses/BSD-3-Clause) |
+| [org.coursera:metrics-datadog](https://github.com/coursera/metrics-datadog/) | [The BSD 3-Clause License](http://opensource.org/licenses/BSD-3-Clause) |
+| [org.cryptacular:cryptacular](http://www.cryptacular.org) | [GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl-3.0.txt) |
+| [org.dom4j:dom4j](http://dom4j.github.io/) | [BSD 3-clause New License](https://github.com/dom4j/dom4j/blob/master/LICENSE) |
+| [org.eclipse.jetty.toolchain.setuid:jetty-setuid-java](https://www.eclipse.org/jetty/) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-client](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-continuation](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-http](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-io](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-security](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-server](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-servlet](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-util](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.eclipse.jetty:jetty-util-ajax](https://eclipse.org/jetty) | [Eclipse Public License v1.0](http://www.eclipse.org/legal/epl-v10.html) |
+| [org.flywaydb:flyway-core](https://flywaydb.org/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.freemarker:freemarker](https://freemarker.apache.org/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.glassfish.hk2.external:aopalliance-repackaged](http://www.oracle.com) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.hk2.external:javax.inject](http://www.oracle.com) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.hk2:hk2-api](http://www.oracle.com) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.hk2:hk2-locator](http://www.oracle.com) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.hk2:hk2-utils](http://www.oracle.com) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.hk2:osgi-resource-locator](https://glassfish.dev.java.net) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| org.glassfish.jaxb:jaxb-core | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| org.glassfish.jaxb:jaxb-runtime | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| org.glassfish.jaxb:txw2 | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.bundles.repackaged:jersey-guava](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.connectors:jersey-apache-connector](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.containers:jersey-container-servlet](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.containers:jersey-container-servlet-core](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.core:jersey-client](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.core:jersey-common](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.core:jersey-server](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.ext.rx:jersey-rx-client](https://jersey.github.io/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.ext:jersey-bean-validation](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.ext:jersey-entity-filtering](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.ext:jersey-metainf-services](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.media:jersey-media-jaxb](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.media:jersey-media-json-jackson](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish.jersey.media:jersey-media-multipart](http://www.oracle.com/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish:javax.el](http://uel.java.net) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish:javax.el](http://el-spec.java.net) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.glassfish:javax.json](http://jsonp.java.net) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.hamcrest:hamcrest-core](http://hamcrest.org/JavaHamcrest/) | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) |
+| [org.hibernate.common:hibernate-commons-annotations](http://hibernate.org) | [GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl-2.1.html) |
+| [org.hibernate.javax.persistence:hibernate-jpa-2.1-api](http://hibernate.org) | [Eclipse Distribution License (EDL), Version 1.0](http://www.eclipse.org/org/documents/edl-v10.php) |
+| [org.hibernate:hibernate-core](http://hibernate.org) | [GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl-2.1.html) |
+| [org.hibernate:hibernate-entitymanager](http://hibernate.org) | [GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl-2.1.html) |
+| [org.hibernate:hibernate-validator](http://hibernate.org/validator/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.javassist:javassist](http://www.javassist.org/) | [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) |
+| [org.jboss.logging:jboss-logging](http://www.jboss.org) | [CC0 v1.0](http://repository.jboss.org/licenses/cc0-1.0.txt) |
+| org.jboss.logging:jboss-logging-annotations | [CC0 v1.0](http://repository.jboss.org/licenses/cc0-1.0.txt) |
+| [org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec](http://www.jboss.org) | [CC0 v1.0](http://repository.jboss.org/licenses/cc0-1.0.txt) |
+| org.jboss:jandex | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.jdbi:jdbi](http://jdbi.org/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.jvnet.mimepull:mimepull](http://mimepull.java.net) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.jvnet.staxex:stax-ex](http://stax-ex.java.net/) | [CDDL v1.1](https://javaee.github.io/glassfish/LICENSE) |
+| [org.ldaptive:ldaptive](http://www.ldaptive.org) | [GNU Lesser General Public License](http://www.gnu.org/licenses/lgpl-3.0.txt) |
+| [org.mariadb.jdbc:mariadb-java-client](https://mariadb.com/kb/en/mariadb/about-mariadb-connector-j/) | [LGPL v2.1](https://www.gnu.org/licenses/lgpl-2.1) |
+| org.opensaml:opensaml-core | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-messaging-api | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-messaging-impl | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-profile-api | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-profile-impl | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-saml-api | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-saml-impl | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-security-api | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-security-impl | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-soap-api | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-soap-impl | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-storage-api | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-storage-impl | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-xmlsec-api | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.opensaml:opensaml-xmlsec-impl | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.owasp.encoder:encoder](https://www.owasp.org/index.php/OWASP_Java_Encoder_Project) | [The BSD 3-Clause License](http://www.opensource.org/licenses/BSD-3-Clause) |
+| org.pac4j.jax-rs:core | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.pac4j:dropwizard-pac4j](https://github.com/pac4j/dropwizard-pac4j) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.pac4j:j2e-pac4j](https://github.com/pac4j/j2e-pac4j) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.pac4j:jersey225-pac4j | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.pac4j:pac4j-config | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.pac4j:pac4j-core | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| org.pac4j:pac4j-saml | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.projectlombok:lombok](https://projectlombok.org) | [MIT License](https://opensource.org/licenses/MIT) |
+| [org.reflections:reflections](http://github.com/ronmamo/reflections) | [The New BSD License](http://www.opensource.org/licenses/bsd-license.html) |
+| [org.simplejavamail:simple-java-mail](http://www.simplejavamail.org/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.slf4j:jcl-over-slf4j](http://www.slf4j.org) | [MIT License](https://opensource.org/licenses/MIT) |
+| [org.slf4j:jul-to-slf4j](http://www.slf4j.org) | [MIT License](https://opensource.org/licenses/MIT) |
+| [org.slf4j:log4j-over-slf4j](http://www.slf4j.org) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.slf4j:slf4j-api](http://www.slf4j.org) | [MIT License](https://opensource.org/licenses/MIT) |
+| [org.springframework:spring-beans](https://github.com/spring-projects/spring-framework) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.springframework:spring-core](https://github.com/spring-projects/spring-framework) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.springframework:spring-jcl](https://github.com/spring-projects/spring-framework) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.springframework:spring-jdbc](https://github.com/spring-projects/spring-framework) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.springframework:spring-orm](https://github.com/spring-projects/spring-framework) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.springframework:spring-tx](https://github.com/spring-projects/spring-framework) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [org.w3c.css:sac](http://www.w3.org/Style/CSS/SAC/) | [The W3C Software License](http://www.w3.org/Consortium/Legal/copyright-software-19980720) |
+| [org.yaml:snakeyaml](http://www.snakeyaml.org) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [xalan:serializer](http://xml.apache.org/xalan-j/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [xalan:xalan](http://xml.apache.org/xalan-j/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+| [xml-apis:xml-apis](http://xml.apache.org/commons/components/external/) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
+
+## Client-side dependencies
+
+| Library | License |
+| --- | ---|
+| [Ionicons@2.0.0](https://github.com/driftyco/ionicons) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-base64@2.0.5](https://github.com/ninjatronic/angular-base64) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-modal-service@0.12.1](https://github.com/dwmkerr/angular-modal-service) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-permission@6.0.0](https://github.com/Narzerus/angular-permission) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-ui-router@0.4.2](https://github.com/angular-ui/ui-router) | [MIT License](https://opensource.org/licenses/MIT) | |
+| [angular-vs-repeat@1.1.8](http://kamilkp.github.io/angular-vs-repeat) | [MIT License](https://opensource.org/licenses/MIT) |
+| [animate.css@3.5.1](https://github.com/daneden/animate.css ) | [MIT License](https://opensource.org/licenses/MIT) |
+| [bourbon@4.2.7](https://github.com/thoughtbot/bourbon.git) | [MIT License](https://opensource.org/licenses/MIT) |
+| [jquery@3.4.1](https://jquery.com/) | [MIT License](https://opensource.org/licenses/MIT) |
+| [moment@2.14.2](https://momentjs.com/) | [MIT License](https://opensource.org/licenses/MIT) |
+| [ng-tags-input@3.1.2](http://mbenford.github.io/ngTagsInput) | [MIT License](https://opensource.org/licenses/MIT) |
+| [normalize-css@3.0.3](https://github.com/necolas/normalize.css) | [MIT License](https://opensource.org/licenses/MIT) |
+| [ngclipboard@1.1.1](https://github.com/sachinchoolur/ngclipboard) | [MIT License](https://opensource.org/licenses/MIT) |
+| [trix](http://trix-editor.org/) | [MIT License](https://opensource.org/licenses/MIT) |
+| [Chart.PieceLabel.js@0.7.0](https://github.com/emn178/Chart.PieceLabel.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angucomplete-alt@3.0.0](http://ghiden.github.io/angucomplete-alt/) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular@1.7.9](https://github.com/angular/angular.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-animate@1.7.9](https://github.com/angular/angular.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-cookies@1.7.9](https://github.com/angular/angular.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-chart.js@1.1.1](https://github.com/jtblin/angular-chart.js) | [BSD](https://github.com/jtblin/angular-chart.js/blob/master/LICENSE) |
+| [angular-messages@1.7.9](https://github.com/angular/angular.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-internationalization@1.2.7](https://github.com/nolazybits/angular-i18n) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-resource@1.7.9](https://github.com/angular/angular.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-touch@1.7.9](https://github.com/angular/angular.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-ui-grid@4.8.3](https://github.com/angular-ui/ui-grid) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-sanitize@1.7.9](https://github.com/angular/angular.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angular-i18n@1.7.9](https://github.com/angular/angular.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [angularjs-datepicker@2.1.23](http://720kb.github.io/angular-datepicker) | [MIT License](https://opensource.org/licenses/MIT) |
+| [core.js@2.4.1](https://github.com/zloirock/core-js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [chart.js@2.6.0](http://www.chartjs.org) | [MIT License](https://opensource.org/licenses/MIT) |
+| [clipboard@1.5.16](https://github.com/zenorocha/clipboard.js) | [MIT License](https://opensource.org/licenses/MIT) |
+| [ngInfiniteScroll@1.2.0](https://github.com/sroze/ngInfiniteScroll) | [MIT License](https://opensource.org/licenses/MIT) |
+| [neat@1.7.4](https://github.com/thoughtbot/neat) | [MIT License](https://opensource.org/licenses/MIT) |
+| [sprintf@1.1.2](https://github.com/alexei/sprintf.js) | [BSD-3-Clause](http://opensource.org/licenses/BSD-3-Clause) |
+| [bitters@1.1.0](https://github.com/thoughtbot/bitters) | [MIT License](https://opensource.org/licenses/MIT) |
+| [lodash@4.17.4](https://github.com/lodash/lodash) | [MIT License](https://opensource.org/licenses/MIT) |
+| [swagger-ui@3.25.4](https://github.com/wordnik/swagger-ui) | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0) |
\ No newline at end of file
diff --git a/doc/content/faq/logs.md b/doc/content/faq/logs.md
new file mode 100644
index 0000000..ef369f6
--- /dev/null
+++ b/doc/content/faq/logs.md
@@ -0,0 +1,27 @@
+---
+title: "Logs"
+type: docs
+menu:
+ main:
+ name: Where Are My Logs?
+ identifier: logs
+ parent: faq
+ weight: 4
+---
+
+# Where Are My Logs?
+
+Logs are located on the application servers. We will ask for these during certain support issues, and they're helpful for you to see if something fishy is happening on your server. Some important logs for Content Controller include:
+
+* `/var/log/contentcontroller.log`
+* `/var/log/scormengine.log`
+* `/var/log/apache2/access.log`
+* `/var/log/apache2/error.log`
+* `/var/log/tomcat/*.log`
+
+You should install some sort of agent to ship your logs somewhere where they are easier to search, store, and manage. This is essential if you start using an autoscaling group.
+
+A few easy-to-use options include:
+
+* [Logz.io](https://logz.io)
+* [Papertrail](https://papertrailapp.com)
diff --git a/doc/content/faq/saml.md b/doc/content/faq/saml.md
new file mode 100644
index 0000000..d6da5b8
--- /dev/null
+++ b/doc/content/faq/saml.md
@@ -0,0 +1,93 @@
+---
+title: "SAML"
+type: docs
+menu:
+ main:
+ name: SAML Integration
+ identifier: saml
+ parent: faq
+ weight: 5
+---
+
+# SAML SSO
+
+## Introduction
+SAML allows an organization to create a single source of truth for user identity management and communicate this information with other applications. The user identity source is referred to as the Identity Provider (IdP) which communicates with Service Providers (SP). In this context, Content Controller is the Service Provider and an application like Active Directory would be the Identity Provider.
+
+The following steps will describe how to integrate your Identity Provider with Content Controller.
+
+## Setup
+Before starting the configuration of SAML on the Content Controller side of things, first you must obtain a metadata XML file from your identity provider and place it in the `roles/saml/files` directory of the playbooks and name it `idp-metadata.xml`.
+
+Next, if you do not already have one, you will need to generate a keystore for all signature and encryption operations. Execute the following commands, replacing `{private_key_password}` and `{keystore_password}` with strong passwords. Keep track of these two passwords as they will be added to the Content Controller configuration later.
+
+```
+keytool -genkeypair -alias pac4j-demo \
+ -keystore samlKeystore.jks -keyalg RSA -keysize 2048 -validity 3650 \
+ -keypass {private_key_password} -storepass {keystore_password}
+```
+
+This command will output a file named `samlKeystore.jks`. Where you run the command isn't important, as long as the `samlKeystore.jks` is copied onto the Ansible control server. It needs to be moved into the `roles/saml/files` directory of the playbooks.
+
+## Content Controller Configuration
+With these files now in the proper place, open your `group_vars/content_controller.yml` configuration file in the Ansible playbooks. Find the placeholder values for these settings and update them to the following:
+
+```
+enable_saml: true
+private_key_password: {private_key_password used to generate keystore}
+keystore_password: {keystore_password used to generate keystore}
+maximum_auth_lifetime: 3600
+include_query_param_on_callback: true
+```
+
+`maximum_auth_lifetime` is the maximum age in seconds that Content Controller will allow for a user. If a token older than this lifetime is used in a request, then CC will reject it. The value configured in CC should be shorter than or equal to the same value in your IdP server.
+
+`include_query_param_on_callback` determines whether or not query parameters are included on the SAML callback URLs.
+This is configurable as some Identify Providers do not accept query parameters. If Content Controller doesn't include
+query parameters, the 'client_name' property will be set as part of the SAML resolver.
+
+### Identifying Users
+
+After your IdP authenticates a user, it will send information about that user to Content Controller in the form of attributes on a profile. By default, CC will use the value of the `email` attribute as the user ID when a user logs in through SSO. However, if there is an attribute on the SAML profile that you would prefer as the identifier, you can use the setting `saml_identifying_attribute`. The value of this setting should be the name of the attribute that should be used to uniquely identify users. The value of the attribute will be used to uniquely identify users coming from your IdP, so please ensure that the value of the attribute is unique for each user.
+
+### Authorizing Users
+
+By default, Content Controller will allow any user that comes from your IdP. However, CC can be configured to authorize users based on the attributes that are sent back your IdP. If you would like to limit the users that can access the application, then you can use the following settings:
+
+* `saml_access_attribute`: The name of the attribute used to authorize users.
+* `saml_access_value`: The value required to be in or equal to the `saml_access_attribute`.
+* `saml_access_condition`: The operation used to verify that the `saml_access_value` is present in the `saml_access_attribute`. Can be `equals`, `starts_with`, or `contains`. If not specified, will default to `contains`.
+
+If the above settings are configured, Content Controller will first verify that the `saml_access_attribute` is present on the profile sent by the IdP. Then, depending on the configured `saml_access_condition`, CC will verify that the attribute value either equals, starts with, or contains the `saml_access_value`.
+
+For example, if you wanted to require that users SSOing into Content Controller have an attribute `teams` that contains the string `content_controller`, then you would set the following:
+
+```
+saml_access_attribute: "teams"
+saml_access_value: "content_controller"
+```
+
+If you wanted to be very strict and require that users have the attribute `cc_admin` set to `true`, then you would set the following:
+
+```
+saml_access_attribute: "cc_admin"
+saml_access_value: "true"
+saml_access_condition: "equals"
+```
+
+## IdP Configuration
+
+To integrate Content Controller with your IdP server, you will need to provide a service provider metadata file. This file is used to configure your IdP so that it knows how to communicate with Content Controller. Because these steps rely on saving a file to disk, if you've multiple app servers behind a load balancer, then this process will be more reliable if you can execute the requests directly against one app server.
+
+After you've deployed Content Controller, first visit the endpoint `[domain or IP]/api/saml`. This will attempt to initiate an SSO login, and probably redirect you over to the IdP. This process will generate a SAML metadata file on disk. If you get an error back from that endpoint, then there may be an issue with your SAML configuration. Refer to the application logs for more details.
+
+Once you've generated the service provider metadata, you can access it by hitting the endpoint `[domain or IP]/api/saml/metadata`. It will return an XML document that you can save to the file system. Then, provide it to your IdP in the appropriate manner. This will vary depending on which IdP you are using; for more information, refer to that product's documentation or support team.
+
+Content Controller will need permission to initiate the SSO process. Some IdP's may require special configuration to enable SP-Initiated SSO. If the specific binding is required, please add `urn:oasis:names:tc:SAML:profiles:SSO:request-init` as one of the allowable SAML bindings.
+
+By default, Content Controller requires an `email` attribute on the profile your IdP provides. If that is not included by default, then you may need to do a bit of extra configuration to ensure that information is communicated to the application. Alternatively, you can refer to the "Identifying Users" section above for how to configure Content Controller to not require an `email` attribute.
+
+
+## Use
+
+Once enabled, a new `SSO` button will appear on the login page that will redirect the user to your Identity Provider login page. If authentication with the IdP is successful, then the user will be redirected back to Content Controller and issued a valid authentication token. If a CC profile already exists with that email address, then the user will be logged in using that profile. If such a user does not already exist in the application, then one will be created.
diff --git a/doc/content/faq/security.md b/doc/content/faq/security.md
new file mode 100644
index 0000000..8d9c39e
--- /dev/null
+++ b/doc/content/faq/security.md
@@ -0,0 +1,105 @@
+---
+title: "Security"
+type: docs
+menu:
+ main:
+ name: Customizing Security Settings
+ identifier: security
+ parent: faq
+ weight: 2
+---
+
+# Customizing Security Settings
+
+By default, Content Controller ships with security settings that work for most people. However, sometimes tighter security is more desireable than supporting older browsers. Most security-related settings can be adjusted by changing a few playbook variables.
+
+## HTTP Strict Transport Security (HSTS)
+
+If you need to support LMSs that only work with HTTP, then you can not use this setting. If you want to force HTTPS access semi-permanently, you can enable [HTTP Strict Transport Security](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security). **Only do this if you're sure that you do not need to support HTTP-only LMSs.** Once the setting is enabled, browsers will be locked to loading over HTTPS only for at least 6 months.
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/env.yml`.
+2. If you're terminating SSL at Apache, edit the line that starts with `allow_80` and set it to `allow_80: false`; if you're terminating SSL at your load balancer (and/or using CloudFront), set it to `allow_80: true`.
+3. Add this line under `# SSL Configuration`: `use_hsts: true`
+4. If you would like to adjust the time of the HTTPS lock, add the line setting the `hsts_max_age` with the desired value in seconds.
+5. If you'd like to include the `includeSubDomains` option in the HSTS header, then also add the line `hsts_include_subdomains: true`. [RFC 6797](https://tools.ietf.org/html/rfc6797#section-14.4) recommends this option, but it should not be used if any subdomain of your CC installation could possibly need to allow HTTP-only access in the future.
+6. Save and exit.
+7. Run the playbooks to deploy your changes.
+
+## Session Timeout
+
+By default, session timeout is 24 hours. The minimum allowed value is 1 minute, and the maximum allowed value is 43200 minutes (30 days).
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/content_controller.yml`.
+2. Add this line `token_exp: 1440`, but replace `1440` with your desired session length in minutes.
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+## Public API Auth Token Expiration
+
+By default, the public API auth token expires after 30 minutes.
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `roles/content-controller/defaults/main.yml`.
+2. Add this line `public_api_auth_expiration: #`, but replace `#` with your desired session length in minutes.
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+## Disabling Inactive User Accounts
+
+Users that have experienced a specific number of days since its last login and last unlock are considered inactive. By default, this behavior is disabled.
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/content_controller.yml`.
+2. Add this line `user_account_days_inactive_threshold: 30`, and replace `30` with your desired inactivity threshold in number of days.
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+## SSL Cipher Suites
+
+Ciphers can be enabled or disabled by supplying an SSL cipher suite config. By default, we use [this config provided by Mozilla's SSL Configuration Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.4.28&openssl=1.0.1f&hsts=no&profile=intermediate). If you don't need to support older browsers, you can use the **SSLCipherSuite** provided by choosing the **Modern** settings.
+
+## Using SSL for Database Traffic
+
+Content Controller can be configured to encrypt traffic to the database using SSL/TLS. At a minimum, you just need to add `db_use_ssl: true` to one of your Ansible config files. By default, Content Controller will trust the certificates used by RDS databases.
+
+If you want to communicate with a database that is not in RDS, then you will also need to provide the certificate of that database in the `roles/ssl/files` directory of your playbooks. Then, configure the setting `db_ssl_cert: [certificate name]`, but replace `[certificate name]` with the name of the file you added to the playbooks.
+
+Additionally, there is an optional setting you can set to control the SSL protocols supported. By default, `db_ssl_protocols` will have the value of `"TLSv1,TLSv1.1,TLSv1.2"`. If you want to add or remove protocols from this default list, then override it by providing your own list of values.
+
+### Terminating SSL at CloudFront
+
+See this AWS documentation on [Supported Protocols and Ciphers](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html), and then update your CloudFront Security Policies and Elastic Load Balancer Security Policies to match. See the [CloudFront page](/self-hosting/aws/cloudfront) for a refresher on setting distribution details, and see the [Load Balancer page](/self-hosting/aws/load-balancer) for a refresher on setting the application load balancer config.
+
+### Terminating SSL at the Application Server
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/env.yml`.
+2. Add this line under `# SSL Configuration`, but replace `...` with your desired cipher suite config: `ssl_cipher_suite: "..."`
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+## SSL Protocol
+
+By default, we allow TLS 1.0, TLS 1.1, and TLS 1.2. If you do not need to support learners using older browsers/operating systems (such as IE 9 and Windows Vista), then you should turn off TLS 1.0.
+
+### SSL Terminated at CloudFront
+
+Check your CloudFront Origin Behaviors and verify that you have set the desired protocols. See the [CloudFront](/self-hosting/aws/cloudfront) docs for a refresher on setting origin behavior details.
+
+### SSL Terminated at the Application Server
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/env.yml`.
+2. Add this line under `# SSL Configuration`: `ssl_protocol: "all -SSLv2 -SSLv3 -TLSv1"`
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
+
+### Configuring Password Reset Limits
+
+Password reset emails can be sent through the 'Forgot Password?' button on the login page. This page does not require authentication to access, therefore we want to protect users against a malicious actor using this functionality to spam them with emails. We check the IP address and requested email of the password reset email request to ensure the combination has not requested a password reset email more than `password_reset_failure_threshold` times in the past `password_reset_failure_window ` days.
+
+Additionally, to protect against a distributed attack with requests to reset a single user's password coming from multiple IP address, we check to see if there have been `2 * password_reset_failure_threshold` password reset requests from _any_ IP addresses in the last `password_reset_failure_window` days.
+
+The maximum number of requests (default of 3 attempts) and the number of days back in which to check those requests (default 1 day) are configurable.
+
+1. On your Ansible control server, go to `ContentController_PublicDeploy` and edit `group_vars/content_controller.yml`.
+2. To change the maximum number of attempts allowed, add `password_reset_failure_threshold: #` but replace `#` with number of maximum attempts you wish to allow.
+3. To change the number of days in which password reset requests are checked against, add `password_reset_failure_window: #` but replace `#` with the number of days back you wish to check. This value should not exceed `30`.
+3. Save and exit.
+4. Run the playbooks to deploy your changes.
\ No newline at end of file
diff --git a/doc/content/img/cc-logo.png b/doc/content/img/cc-logo.png
new file mode 100644
index 0000000..941772e
Binary files /dev/null and b/doc/content/img/cc-logo.png differ
diff --git a/doc/content/img/infrastructure-high-level.png b/doc/content/img/infrastructure-high-level.png
new file mode 100644
index 0000000..da10b45
Binary files /dev/null and b/doc/content/img/infrastructure-high-level.png differ
diff --git a/doc/content/infrastructure.md b/doc/content/infrastructure.md
new file mode 100644
index 0000000..d11c2a6
--- /dev/null
+++ b/doc/content/infrastructure.md
@@ -0,0 +1,35 @@
+---
+title: "Hosting at Scale"
+type: docs
+menu: "main"
+weight: 2
+---
+
+# Hosting at Scale
+
+When you move to production with Content Controller, security and uptime become important. If you follow the [Deploying in AWS](/self-hosting/aws/aws) instructions, then we'll explain how to deploy with these concerns in mind.
+
+## Production Environment
+
+These architecture diagrams look at what a production-grade Content Controller environment should look like in Amazon Web Services. If you aren't using AWS, it should still be similar (minus CloudFront and S3).
+
+### High Level Overview
+{{< img src="/self-hosting/img/infrastructure-high-level.png" >}}
+
+### Networking and Security Overview
+
+{{< img src="/self-hosting/aws/img/vpc-diagram.png" >}}
+
+Some key points to take from the diagrams:
+
+* Application and database servers should be in a private subnet (inaccessible from the outside world) and should be spread across availability zones, if possible.
+* Your Ansible server will need to be in the public subnet along with the load balancer, or you will need a VPN connection to be able to access the Ansible server via SSH.
+* Database access should occur through a VPN or through the Ansible server over an SSH tunnel.
+
+## Managed Hosting
+
+We also provide managed hosting services for Content Controller, which can be a great alternative if you need to get a large-scale production installation up and running quickly.
+
+## More information
+
+https://support.scorm.com/hc/en-us/categories/115000007313-Hosting-Infrastructure-and-Servers
diff --git a/doc/content/quick-start.md b/doc/content/quick-start.md
new file mode 100644
index 0000000..9f3f886
--- /dev/null
+++ b/doc/content/quick-start.md
@@ -0,0 +1,105 @@
+---
+title: "Installation Guide"
+type: docs
+menu: "main"
+weight: 5
+---
+
+# Installation Guide
+
+This Installation Guide is for someone who has a working knowledge of AWS and wants to jump right in or someone who is planning to host Content Controller on a different platform (such as Azure, Google Cloud, bare metal, etc). If you want detailed deployment steps for AWS, take a look at the [Deploying in AWS](/self-hosting/aws/aws) section.
+
+Before you start, read through the [Requirements](/self-hosting/requirements), [Hosting at Scale](/self-hosting/infrastructure), and [Deploy Tools Reference](/self-hosting/deploy-tools) sections, and make sure you have a good understanding of how the Ansible playbooks are structured.
+
+## Prepare Your Infrastructure
+
+1. Setup your hosting environment
+ * Public and private subnets
+ * Shared storage for content (such as an NFS or S3 compatible object storage)
+ * Load balancer
+ * If your load balancer is terminating SSL, then make sure that it is sending the [X-Forwarded-Proto header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto).
+ * Content delivery network
+2. Create your control server
+ * Running Ubuntu 18.04 or greater
+ * Accessible via SSH
+ * Has privileged access to your application servers
+
+## Prepare Your Control Server
+
+1. SSH to your control server.
+2. Install `git`.
+3. Run `git clone https://github.com/RusticiSoftware/ContentController-PublicDeploy.git`.
+4. Change to the `ContentController-PublicDeploy` folder.
+5. Checkout the appropriate branch for your release, e.g. `git checkout vx.x.xxx`. Refer to [our release notes](https://support.scorm.com/hc/en-us/sections/115000419513-Release-Notes) for the latest version number.
+6. Run `./bootstrap.sh` to install Ansible, python, boto, and other dependencies.
+7. Copy the `keypair.yml` file provided by Rustici into `group_vars/keypair.yml`.
+8. Run `./setup.sh cc.example.com`, but replace `cc.example.com` with your FQDN for Content Controller.
+9. Take note of the DB `mysql_root` user and password from `group_vars/env.yml`.
+
+## Prepare Your Database
+
+1. Setup your MySQL using the root username and password copied from the last steps. Or, if a root user was previously configured, update the `mysql_root_password` in `group_vars/env.yml` to match your root user's password.
+2. Make sure the root user is set up to allow remote connections from your application servers.
+3. Set the database server's timezone to UTC.
+4. Apply the following settings:
+
+### Binary Logging of Stored Programs
+
+If you're using Amazon RDS, or are running on a server that is doing binary logging (which is most modern MySQL servers) and upon which your user lacks the SUPER privilege, you'll need to set the global variable `log_bin_trust_function_creators` to `1` in the parameter group associated with your Content Controller databases. You can configure this with either `SET PERSIST log_bin_trust_function_creators = 1;` or by configuring it in the MySQL config files.
+
+If you don't do this, Content Controller database migrations that use triggers will fail, which will cause problems during deployment.
+
+For more information, please see:
+
+https://aws.amazon.com/premiumsupport/knowledge-center/rds-mysql-functions/
+
+https://dev.mysql.com/doc/refman/5.6/en/stored-programs-logging.html
+
+### SQL Mode
+
+Due to some changes made to the way `JOIN`s work in MySQL 5.7 (`ONLY_FULL_GROUP_BY`), you will need to disable that SQL mode. To minimize the differences between your system and our QA systems, we recommend that you use the AWS RDS default `sql_mode` of `NO_ENGINE_SUBSTITUTION`. Again, this can be configured with `SET PERSIST` or in the MySQL config files on your database server.
+
+For more information, please see:
+
+https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html
+
+## Configure Shared Storage
+
+If you want to use S3 and CloudFront, follow the detailed documentation in [Deploying to AWS](/self-hosting/aws/aws). You can use these even if you are hosting your application with a different provider (although the CloudFront bits will be more complicated).
+
+If you're using a non-AWS S3 compatible object store, those settings will go in `group_vars/s3.yml`. Note that you will need to provide a way to serve these files manually on the same domain that Content Controller is hosted from at the `/courses/*` path. We use CloudFront for this.
+
+If you're using an NFS, mount it on your application servers at `/mnt` (or mount it where you want and update the `data_root` settings in your Ansible config). Apache will handle serving your content from the NFS using the configured path. As part of mounting the NFS onto your app severs, make sure that you also configure it so the NFS remounts automatically when the app servers are rebooted.
+
+And if you're only using one application server, you can host the content on the server itself. This is the default config.
+
+## Prepare your environment config
+
+1. Go to the `ContentController-PublicDeploy` folder on your control server.
+2. "Go through each of the files in the `group_vars` directory to ensure your settings are correct. This includes settings such as the DB endpoint, setting up email alerts, your SSL settings, etc). Refer to the comments in those files and the [Deploy Scripts Reference](/self-hosting/deploy-tools) for details on these settings. If you don't go through and confirm these settings, the deployment will likely fail or deploy an invalid configuration.
+
+## Deploy
+
+If you're using AWS, refer to the details about [Building and Deploying an AMI](/self-hosting/aws/aws). If you're using fixed machines and want to deploy straight to them:
+
+1. Setup your application servers. These should be a base Ubuntu 14.04 or later install. Take note of their IPs, the SSH user name, and the SSH key.
+2. Go to the `ContentController-PublicDeploy` folder on your control server.
+3. Copy `inventory` to `inventory.prod`.
+4. Edit the inventory and delete the dummy IP addresses replacing them with the IP addresses to your application servers.
+5. Run the Ansible playbooks against your production servers:
+```
+ansible-playbook --user=YOUR_SSH_USER --connection=ssh --inventory-file=inventory.prod env.yml
+```
+If you use a private key to SSH onto your app server, then you will need to include the flag `--private-key="/path/to/my_private_key.pem"`. If you use username/password for SSH, then you may need to include the flag `--ask-sudo-pass` on your `ansible-playbooks` command.
+
+Note: Content Controller is built around its sister product, Rustici Engine. If you are deploying an update between major versions (e.g. 2.1.xxx -> 2.2.xx or 2.2.xx -> 3.0.x), we need to also upgrade Engine's database schema. To do this, you wil need to add the argument `-e engine_upgrade=true` when running the command above. This flags tells Ansible that this particular deployment will require the Engine upgrade. You won't be required to use this flag when moving between maintenance releases (2.2.78 -> 2.2.81) unless specified in the release notes.
+
+## Upgrading
+
+1. Take a look at the [Release Notes](https://support.scorm.com/hc/en-us/sections/115000419513-Release-Notes) to see if there are any important messages about self-hosting for any of the versions between the one you're starting on and the one you're moving to.
+2. Go to the `ContentController-PublicDeploy` folder on your control server.
+3. Create an image of your DB, control server, and an application server in case something goes sideways during the upgrade.
+4. If you have made any changes or customizations to the playbooks during a previous deployment, then first run `git stash` to capture a snapshot of those changes. This is to ensure that your changes are not lost when checking out the latest branch.
+5. Run `git pull`, then run `git checkout vx.x.xxx` replacing `x.x.xxx` with the version number you are moving to.
+6. If you stashed any changes during step 4, then you should now reapply those changes by running `git stash apply`.
+7. Run the ansible-playbook command described in the *Deploy* section.
diff --git a/doc/content/requirements.md b/doc/content/requirements.md
new file mode 100644
index 0000000..f3ddae3
--- /dev/null
+++ b/doc/content/requirements.md
@@ -0,0 +1,108 @@
+---
+title: "Infrastructure Requirements"
+type: docs
+menu: "main"
+weight: 1
+---
+
+# Infrastructure Requirements
+
+Content Controller has four required components: a database to store application data, file storage for courses and other application data, an application server, and a control server.
+
+## Minimum Requirements
+
+### Database
+
+Content Controller requires MySQL 5.7 or 8.0. We recommend a MySQL 5.7/8.0 installation dedicated to Content Controller. For details about specific settings that will be required in your MySQL instance, see [Installation Guide](/self-hosting/quick-start) or [RDS Setup](/self-hosting/aws/rds).
+
+### File Storage
+
+If you're running more than one Content Controller application server (which we highly recommend for production), you will need some kind of shared storage mechanism for your SCORM content. SCORM content has to live on a filesystem somewhere (it does not get persisted in the database).
+
+If you're using AWS, we recommend using S3 for content storage and CloudFront for distribution. We designed Content Controller to use these technologies, and we test against them with every build. For a high-performance setup that doesn't cost a fortune, this is the way to go.
+
+If you prefer not to use S3 and CloudFront, here are some alternative options:
+
+- An iSCSI target
+- A direct attached SAN of some sort
+- An NFS share
+- We don't recommend using SMB shares. You might be able to make it work fine, but please don't ask us to support it.
+
+Storage is the most difficult aspect of sizing to predict - it all depends upon your content. If you're unsure, talk to us about your content and we'll help you find a way to spec an environment to suit.
+
+### Application Server
+
+Content Controller requires _at least_ one server used only for running the Content Controller application. We support Ubuntu 18.04 LTS and 20.04 LTS, and Red Hat/CentOS 7 and 8. We recommend using Ubuntu, as we have the most experience with that OS, but we can support Red Hat deployments if that is preferred by your team.
+
+This server will contain Apache, Tomcat, Content Controller, SCORM Engine, Java, temporary files, logs, etc. But don't worry about the long list - we've created some [Ansible playbooks](/self-hosting/deploy-tools) to help you deploy and configure these dependencies in a way that's tested and reliable. These playbooks require an Ansible version of at least 2.8.6.
+
+**Note**: Content Controller's deployment process involves downloading dependencies onto the application server. Due to this, we strongly recommend allowing your app servers to connect to the internet during deployment. If that is not possible, then you will need to pull the list of dependencies from the Ansible playbooks, download the necessary packages, and deploy them onto the server.
+
+### Control Server
+
+You will need a place to store your Ansible tools, configuration secrets, etc. You need to use a separate box from your application servers for this purpose, but you can share the Ansible box for deploying to production and staging. If you already have a Jenkins setup or other infrastructure orchestration box, feel free to use it. The only requirements are that it runs Linux (Ubuntu 18.04 is recommended), can run Ansible, has SSH access to your application servers, and is kept secure and backed up.
+
+## Server Sizing
+
+### QA / Staging Environments
+
+If you're customizing the playbooks or the base server for your own needs, you'll probably want to run a small staging environment to test things out before moving to production. We run our QA environment on a t2.medium EC2 instance which has the following specs:
+
+- 4 GB RAM
+- 2 vCPU (2.4 GHz Xeon)
+
+This box is running the entire stack, database and all, and works fine for test purposes. We also run it successfully in a Parallels VM of similar specs for dev stuff.
+
+### Production Environments
+
+Every environment is unique, but we've found that the following guidelines are a great starting point.
+
+#### Entry Level
+
+In our hosted environments, we have successfully served loads of 100 course launches per hour with two AWS EC2 t2.medium application servers and a single RDS t2.small database server. In bare-metal terms, that translates into:
+
+Application Servers:
+
+- 2 servers running behind a load balancer
+- 4 GB RAM each
+- 2 vCPU (2.4 GHz Xeon) each
+- 80 GB of local storage (not content storage)
+
+Database Server:
+
+- 2 GB RAM
+- 1 vCPU (2.4 GHz Xeon)
+
+Control Server:
+
+- 1 GB RAM
+- 1 vCPU
+
+#### Really Big
+
+In our hosted environments, we are able to serve loads of up to 2500 course launches per hour with 5 AWS EC2 t2.medium app servers and a single RDS r5.2xlarge database backend. In bare-metal terms, that translates into:
+
+Application Servers:
+
+- 5 servers running behind a load balancer
+- 4 GB RAM each
+- 2 vCPUs (2.4 GHz Xeon) each
+- 80 GB of local storage (not content storage)
+
+Database Server:
+
+- 61 GB RAM
+- 8 vCPUs (2.4 GHz Xeon)
+
+Control Server:
+
+- 1 GB RAM
+- 1 vCPU
+
+#### Your Environment
+
+If you have questions, give us a call, and tell us about your environment. We can help you spec something appropriate and cost-effective.
+
+## Hosting Providers
+
+We highly recommend using Amazon Web Services, but it is not required. We've provided detailed instructions for running the application in AWS, and we've provided playbooks to automate several of the deploy steps.
diff --git a/doc/content/s3-and-cloudfront.md b/doc/content/s3-and-cloudfront.md
new file mode 100644
index 0000000..1e098ee
--- /dev/null
+++ b/doc/content/s3-and-cloudfront.md
@@ -0,0 +1,22 @@
+---
+title: "S3 and CloudFront"
+type: docs
+menu: "main"
+weight: 4
+---
+
+# S3 and CloudFront
+
+If you're running more than 1 server, you'll need a shared place to store your content. For this, we recommend using AWS S3. You could also use some sort of NFS if you'd like, but we provide application level support for S3. To serve S3 course content, you'll also need to use CloudFront. An additional benefit of CloudFront is that it lets us require authentication for accessing your course content, which prevents random users from being able to scan and download things from your S3 bucket.
+
+CloudFront acts like a reverse proxy in front of your Content Controller application server and the S3 bucket that you use for storing course content. For most connections, it just passes the request through to your web tier. For course content requests, CloudFront authenticates the user via a signed cookie that the application generates, and if the user is allowed, it serves up the content from your S3 bucket.
+
+CloudFront does all this while still appearing to have a DNS origin that is the same whether the content is coming from the web tier or the S3 bucket, which allows us to deliver SCORM content without violating cross-domain rules.
+
+For more information, see the [S3](/self-hosting/aws/s3) and [CloudFront](/self-hosting/aws/cloudfront) sections of [Deploying in AWS](/self-hosting/aws/aws). For alternatives to CloudFront and S3, take a look at the [Hosting at Scale](/self-hosting/infrastructure) section, or give us a call.
+
+## Resources
+
+[Serving Private Content In CloudFront](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html)
+
+[Cookie Signing Information](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html)
diff --git a/doc/install.sh b/doc/install.sh
new file mode 100755
index 0000000..1971d09
--- /dev/null
+++ b/doc/install.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+brew install hugo
\ No newline at end of file
diff --git a/doc/layouts/shortcodes/img.html b/doc/layouts/shortcodes/img.html
new file mode 100644
index 0000000..487028a
--- /dev/null
+++ b/doc/layouts/shortcodes/img.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/doc/themes/rustici-docs-hugo-theme/archetypes/docs.md b/doc/themes/rustici-docs-hugo-theme/archetypes/docs.md
new file mode 100644
index 0000000..e215100
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/archetypes/docs.md
@@ -0,0 +1,4 @@
+---
+title: "{{ .Name | humanize | title }}"
+weight: 1
+---
diff --git a/doc/themes/rustici-docs-hugo-theme/assets/_markdown.scss b/doc/themes/rustici-docs-hugo-theme/assets/_markdown.scss
new file mode 100644
index 0000000..21ba8ae
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/assets/_markdown.scss
@@ -0,0 +1,71 @@
+@import 'variables';
+
+$block-border-radius: 0.15rem;
+
+.markdown {
+ line-height: 1.7;
+
+ h1, h2, h3, h4, h5 {
+ font-weight: 400;
+ line-height: 1.25;
+
+ // remove padding at the beginning of page
+ &:first-child {
+ margin-top: 0;
+ line-height: 1em;
+ }
+ }
+
+ b, optgroup, strong { font-weight: 700; }
+
+ a {
+ text-decoration: none;
+
+ &:hover { text-decoration: underline; }
+ }
+
+ code {
+ font-family: 'Mono', monospace;
+
+ padding: 0 $padding-4;
+ background: $gray-100;
+ border-radius: $block-border-radius;
+ }
+
+ pre {
+ padding: $padding-16;
+ background: $gray-100;
+ border-radius: $block-border-radius;
+ font-size: $font-size-14;
+ overflow-x: auto;
+
+ code {
+ padding: 0;
+ background: none;
+ }
+ }
+
+ blockquote {
+ border-left: $padding-1*2 solid $gray-300;
+ margin: 0;
+ padding: $padding-1 $padding-16;
+
+ :first-child { margin-top: 0; }
+ :last-child { margin-bottom: 0; }
+ }
+
+ table {
+ border-spacing: 0;
+ border-collapse: collapse;
+
+ tr th, tr td {
+ padding: $padding-8 $padding-16;
+ line-height: 1.1;
+ border: 1px solid $gray-200;
+ }
+
+ tr td { line-height: 1.5; }
+
+ tr:nth-child(2n) { background: $gray-100; }
+ }
+}
diff --git a/doc/themes/rustici-docs-hugo-theme/assets/_utils.scss b/doc/themes/rustici-docs-hugo-theme/assets/_utils.scss
new file mode 100644
index 0000000..854c7f2
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/assets/_utils.scss
@@ -0,0 +1,30 @@
+.flex { display: flex; }
+
+.justify-start { justify-content: flex-start; }
+
+.justify-end { justify-content: flex-end; }
+
+.justify-center { justify-content: center; }
+
+.justify-between { justify-content: space-between; }
+
+.align-center { align-items: center; }
+
+.mx-auto { margin: 0 auto; }
+
+.mr-auto { margin-right: auto; }
+
+.hide { display: none; }
+
+@mixin fixed {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+@mixin dark-links {
+ a { color: $color-dark-link; }
+ a.active { color: $color-link; }
+}
diff --git a/doc/themes/rustici-docs-hugo-theme/assets/_variables.scss b/doc/themes/rustici-docs-hugo-theme/assets/_variables.scss
new file mode 100644
index 0000000..aa3ab17
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/assets/_variables.scss
@@ -0,0 +1,48 @@
+// Padding Helpers
+$padding-1: 1px;
+$padding-4: 0.25rem;
+$padding-8: 0.5rem;
+$padding-16: 1rem;
+
+// Font Sizes
+$font-size-base: 16px;
+$font-size-12: 0.75rem;
+$font-size-14: 0.875rem;
+$font-size-16: 1rem;
+
+// Common Colors
+$white: #ffffff;
+
+
+$sidebar-background: #ecf0f2;
+$sidebar-width: 375px;
+
+// Grayscale
+$gray-100: #f8f9fa;
+$gray-200: #e9ecef;
+$gray-300: #dee2e6;
+$gray-400: #ced4da;
+$gray-500: #adb5bd;
+$gray-600: #868e96;
+$gray-700: #495057;
+$gray-800: #343a40;
+$gray-900: #212529;
+$black: #000;
+
+// Link Colors
+$color-link: #004ed0;
+$color-visited-link: #8440f1;
+$color-dark-link: $gray-800;
+
+$body-background: white;
+$body-font-color: $gray-800;
+$body-font-weight: 400;
+
+// Body Size
+// $body-min-width: 900px;
+$docs-max-width: 900px;
+$toc-min-width: 300px;
+
+// Responsive Breakpoints
+$sm-breakpoint: 768px;
+$md-breakpoint: $sidebar-width + 900px + $toc-min-width;
diff --git a/doc/themes/rustici-docs-hugo-theme/assets/core.scss b/doc/themes/rustici-docs-hugo-theme/assets/core.scss
new file mode 100644
index 0000000..f18e735
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/assets/core.scss
@@ -0,0 +1,180 @@
+@import 'normalize';
+@import 'variables';
+@import 'markdown';
+@import 'utils';
+
+html {
+ font-size: $font-size-base;
+ letter-spacing: 0.33px;
+}
+
+html, body {
+ // min-width: $body-min-width;
+ overflow-x: hidden;
+}
+
+body {
+ color: $body-font-color;
+ background: $body-background;
+
+ font-family: 'Poppins';
+ font-weight: $body-font-weight;
+
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ box-sizing: border-box;
+ * { box-sizing: inherit; }
+}
+
+.markdown {
+ h1, h2, h3, h4, h5 {
+ font-weight: 600;
+ }
+}
+
+a {
+ text-decoration: none;
+ color: $color-link;
+
+ &:visited { color: $color-visited-link; }
+}
+
+img { vertical-align: middle; }
+
+aside nav ul {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+
+ li { margin: 1em 0; }
+ a { display: block; }
+ a:hover { opacity: .5; }
+ ul { padding-left: $padding-16; }
+}
+
+.container {
+ width: 100vw;
+ position: relative;
+}
+
+.docs-brand { margin-top: 0; }
+
+.sidebar-menu {
+ width: $sidebar-width;
+ flex: 0 0 $sidebar-width;
+ font-size: $font-size-14;
+ nav { @include fixed; }
+
+ @include dark-links;
+}
+
+.docs-page {
+ max-width: $docs-max-width;
+ position: relative;
+ left: $sidebar-width;
+
+ flex-grow: 1;
+ padding: 45px;
+}
+
+.docs-header {
+ margin-bottom: $padding-16;
+ display: none;
+}
+
+#mobile-toc { display: none; }
+
+.toc {
+ left: $sidebar-width + 900px;
+ font-size: $font-size-14;
+ background-color: #ecf0f2;
+ border-left: 1px solid #e0e0e0;
+ width: 100%;
+
+ @include fixed;
+
+ a { color: #343a40; }
+ nav { padding: 10px 0; }
+ nav > ul > li { margin: 0; }
+ nav > ul > li > a { text-decoration: none; }
+}
+
+.docs-footer {
+ display: flex;
+ margin-top: $padding-16;
+ font-size: $font-size-14;
+ align-items: baseline;
+
+ img {
+ width: $font-size-14;
+ vertical-align: bottom;
+ }
+}
+
+.docs-home { padding: $padding-16; }
+
+// Responsive styles
+aside nav,
+.docs-page,
+.docs-posts,
+.markdown {
+ transition: 0.2s ease-in-out;
+ transition-property: transform, margin-left, opacity;
+ will-change: transform, margin-left;
+}
+
+.u-max-full-width {
+ max-width: 100%;
+ box-sizing: border-box;
+}
+
+@media screen and (max-width: $md-breakpoint) {
+ .docs-page {
+ width: 100%;
+ max-width: 70%;
+ }
+
+ .toc {
+ left: initial;
+ right: -$toc-min-width;
+ width: $toc-min-width;
+ }
+ #mobile-toc {
+ display: block;
+ position: fixed;
+ top: 46px;
+ right: 10px;
+ cursor: pointer;
+ }
+}
+
+@media screen and (max-width: 1168px) {
+ .docs-page { max-width: 60%;}
+}
+
+@media screen and (max-width: $sm-breakpoint) {
+ .sidebar-menu { left: -$sidebar-width; }
+ .docs-header { display: flex; }
+ .docs-page {
+ left: 0 !important;
+ max-width: 100%;
+ }
+ #left-nav { background-color: #fafafa; }
+
+ #menu-control:checked + main {
+ .docs-menu nav,
+ .docs-page,
+ .docs-posts { transform: translateX($sidebar-width); }
+ .sidebar-menu { transform: translateX($sidebar-width); }
+ .docs-header label { transform: rotate(90deg); }
+ .markdown { opacity: 0.25; }
+ }
+}
+
+// Only display one level deep on the table of contents
+#TableOfContents > ul > li > ul { display: none; }
+#TableOfContents > ul > li { padding: 8px; }
+
+@import 'override'
\ No newline at end of file
diff --git a/doc/themes/rustici-docs-hugo-theme/assets/normalize.css b/doc/themes/rustici-docs-hugo-theme/assets/normalize.css
new file mode 100644
index 0000000..1a9fc1b
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/assets/normalize.css
@@ -0,0 +1,349 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+ ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+ html {
+ line-height: 1.15; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+ }
+
+ /* Sections
+ ========================================================================== */
+
+ /**
+ * Remove the margin in all browsers.
+ */
+
+ body {
+ margin: 0;
+ }
+
+ /**
+ * Render the `main` element consistently in IE.
+ */
+
+ main {
+ display: block;
+ }
+
+ /**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+ h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+ }
+
+ /* Grouping content
+ ========================================================================== */
+
+ /**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+ hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+ }
+
+ /**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+ pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+ }
+
+ /* Text-level semantics
+ ========================================================================== */
+
+ /**
+ * Remove the gray background on active links in IE 10.
+ */
+
+ a {
+ background-color: transparent;
+ }
+
+ /**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+ abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+ }
+
+ /**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+ b,
+ strong {
+ font-weight: bolder;
+ }
+
+ /**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+ code,
+ kbd,
+ samp {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+ }
+
+ /**
+ * Add the correct font size in all browsers.
+ */
+
+ small {
+ font-size: 80%;
+ }
+
+ /**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+ sub,
+ sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+ }
+
+ sub {
+ bottom: -0.25em;
+ }
+
+ sup {
+ top: -0.5em;
+ }
+
+ /* Embedded content
+ ========================================================================== */
+
+ /**
+ * Remove the border on images inside links in IE 10.
+ */
+
+ img {
+ border-style: none;
+ }
+
+ /* Forms
+ ========================================================================== */
+
+ /**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+ button,
+ input,
+ optgroup,
+ select,
+ textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+ }
+
+ /**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+ button,
+ input { /* 1 */
+ overflow: visible;
+ }
+
+ /**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+ button,
+ select { /* 1 */
+ text-transform: none;
+ }
+
+ /**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+ button,
+ [type="button"],
+ [type="reset"],
+ [type="submit"] {
+ -webkit-appearance: button;
+ }
+
+ /**
+ * Remove the inner border and padding in Firefox.
+ */
+
+ button::-moz-focus-inner,
+ [type="button"]::-moz-focus-inner,
+ [type="reset"]::-moz-focus-inner,
+ [type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+ }
+
+ /**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+ button:-moz-focusring,
+ [type="button"]:-moz-focusring,
+ [type="reset"]:-moz-focusring,
+ [type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+ }
+
+ /**
+ * Correct the padding in Firefox.
+ */
+
+ fieldset {
+ padding: 0.35em 0.75em 0.625em;
+ }
+
+ /**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+ legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+ }
+
+ /**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+ progress {
+ vertical-align: baseline;
+ }
+
+ /**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+ textarea {
+ overflow: auto;
+ }
+
+ /**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+ [type="checkbox"],
+ [type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+ }
+
+ /**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+ [type="number"]::-webkit-inner-spin-button,
+ [type="number"]::-webkit-outer-spin-button {
+ height: auto;
+ }
+
+ /**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+ [type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+ }
+
+ /**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+ [type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+ }
+
+ /**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+ ::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+ }
+
+ /* Interactive
+ ========================================================================== */
+
+ /*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+ details {
+ display: block;
+ }
+
+ /*
+ * Add the correct display in all browsers.
+ */
+
+ summary {
+ display: list-item;
+ }
+
+ /* Misc
+ ========================================================================== */
+
+ /**
+ * Add the correct display in IE 10+.
+ */
+
+ template {
+ display: none;
+ }
+
+ /**
+ * Add the correct display in IE 10.
+ */
+
+ [hidden] {
+ display: none;
+ }
diff --git a/doc/themes/rustici-docs-hugo-theme/assets/override.scss b/doc/themes/rustici-docs-hugo-theme/assets/override.scss
new file mode 100644
index 0000000..7f193ac
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/assets/override.scss
@@ -0,0 +1,37 @@
+@import url('https://fonts.googleapis.com/css?family=Oxygen:700|Poppins:300');
+
+.hidden { display: none; }
+h1 { color: #335ca6; }
+
+.sidebar-menu {
+ background-color: #fafafa;
+ border-right: 1px solid #e0e0e0;
+ position: fixed;
+}
+
+#left-nav {
+ position: relative;
+ height: 100vh;
+
+ ul {
+ font-size: 0.9rem;
+ li {
+ a { padding: 2px 20px; }
+ }
+
+ ul {
+ .active {
+ margin-left: -1rem;
+ a { margin-left: 1rem; }
+ }
+ }
+ }
+}
+
+.active {
+ background-color: #e6e6e6;
+ padding: 8px 0;
+ margin: 0;
+}
+
+blockquote { background-color: #fafafa; }
diff --git a/doc/themes/rustici-docs-hugo-theme/layouts/404.html b/doc/themes/rustici-docs-hugo-theme/layouts/404.html
new file mode 100644
index 0000000..470efd6
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/layouts/404.html
@@ -0,0 +1 @@
+
NOT FOUND
diff --git a/doc/themes/rustici-docs-hugo-theme/layouts/_default/_markup/render-link.html b/doc/themes/rustici-docs-hugo-theme/layouts/_default/_markup/render-link.html
new file mode 100644
index 0000000..a6e54ab
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/layouts/_default/_markup/render-link.html
@@ -0,0 +1 @@
+{{ .Text }}
\ No newline at end of file
diff --git a/doc/themes/rustici-docs-hugo-theme/layouts/docs/baseof.html b/doc/themes/rustici-docs-hugo-theme/layouts/docs/baseof.html
new file mode 100644
index 0000000..8efb472
--- /dev/null
+++ b/doc/themes/rustici-docs-hugo-theme/layouts/docs/baseof.html
@@ -0,0 +1,26 @@
+
+{{- partial "docs/shared" -}}
+
+
+
+ {{ partial "docs/html-head" . }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/stylesheets/normalize.css b/docs/stylesheets/normalize.css
new file mode 100644
index 0000000..30366a6
--- /dev/null
+++ b/docs/stylesheets/normalize.css
@@ -0,0 +1,424 @@
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+ overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ * Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit; /* 1 */
+ font: inherit; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+ overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+ line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */ /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+ font-weight: bold;
+}
+
+/* Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+td,
+th {
+ padding: 0;
+}
diff --git a/env-novars.yml b/env-novars.yml
new file mode 100644
index 0000000..2a8b4cc
--- /dev/null
+++ b/env-novars.yml
@@ -0,0 +1,29 @@
+---
+
+- hosts: all
+ become: true
+ force_handlers: True
+ gather_facts: false
+ pre_tasks:
+ - name: Wait before running apt update
+ pause:
+ seconds: 30
+ # Install Ansible dependencies (most Ubuntu 18.04, CentOS 8, and Red Hat 8 images don't ship with these by default)
+ - name: Install Ansible dependencies, if necessary
+ raw: if [ -e /usr/bin/apt ]; then (apt -y update && export DEBIAN_FRONTEND=noninteractive && apt install -y python-minimal aptitude); fi; if [ -e /usr/bin/yum ]; then (yum -y update && yum -y install python3); fi;
+ # Force gather_facts to run, now that python is installed
+ - setup:
+ roles:
+ - common
+ - java
+ - ssl
+ - tomcat
+ - mnt
+ - { role: mysql-local, when: cc_db_host == "localhost" }
+ - { role: mysql-config, when: initialize_mysql }
+ - apache
+ - content-controller
+ - cc-scorm-engine
+ - { role: cloudfront, when: use_cloudfront is defined and use_cloudfront|bool == True }
+ - { role: newrelic, when: newrelic_license_key is defined }
+ - { role: saml, when: enable_saml is defined and enable_saml|bool == True }
diff --git a/env.yml b/env.yml
new file mode 100644
index 0000000..f098698
--- /dev/null
+++ b/env.yml
@@ -0,0 +1,43 @@
+---
+
+- hosts: all
+ become: true
+ force_handlers: True
+ gather_facts: false
+ pre_tasks:
+ - name: Wait before running apt update
+ pause:
+ seconds: 30
+ # Install Ansible dependencies (most Ubuntu 18.04, CentOS 8, and Red Hat 8 images don't ship with these by default)
+ - name: Install Ansible dependencies, if necessary
+ raw: if [ -e /usr/bin/apt ]; then (apt -y update && export DEBIAN_FRONTEND=noninteractive && apt install -y python3 aptitude); fi; if [ -e /usr/bin/yum ]; then (yum -y update rpm && yum -y update && yum -y install python3); fi;
+
+ # Force gather_facts to run, now that python is installed
+ - setup:
+
+ # Now that gather_facts has run, set the python interpreter based on the OS
+ - name: Set ansible_python_interpreter
+ set_fact:
+ ansible_python_interpreter: "{{ '/usr/bin/python' if ansible_distribution_major_version == '7' else '/usr/bin/python3' }}"
+ vars_files:
+ - [ "group_vars/{{ env }}.yml", "group_vars/env.yml" ]
+ - group_vars/engine_java.yml
+ - group_vars/content_controller.yml
+ - group_vars/s3.yml
+ - group_vars/cloudfront.yml
+ - group_vars/keypair.yml
+ roles:
+ - common
+ - java
+ - ssl
+ - tomcat
+ - mnt
+ - { role: mysql-local, when: cc_db_host == "localhost" }
+ - { role: mysql-config, when: initialize_mysql }
+ - apache
+ - content-controller
+ - { role: fips, when: fips_support_enabled == True }
+ - cc-scorm-engine
+ - { role: cloudfront, when: use_cloudfront is defined and use_cloudfront|bool == True }
+ - { role: newrelic, when: newrelic_license_key is defined }
+ - { role: saml, when: enable_saml is defined and enable_saml|bool == True }
diff --git a/group_vars/all b/group_vars/all
new file mode 100644
index 0000000..e69de29
diff --git a/group_vars/aws.yml.template b/group_vars/aws.yml.template
new file mode 100644
index 0000000..8ec7dd6
--- /dev/null
+++ b/group_vars/aws.yml.template
@@ -0,0 +1,23 @@
+---
+
+# These vars only need to be set if you're using the build_ami.yml playbook to create your own AMIs
+
+# The AWS region for your VPC
+aws_region: us-east-1
+
+# The ID of your first private VPC subnet (application server subnet)
+ami_build_vpc_subnet_id: # subnet-xxxxxxxx
+
+# The ID for your application server security group
+ami_build_security_group_id: # sg-xxxxxxxx
+
+# The temporary instance size used for building the AMI
+instance_size: t2.medium
+
+# The name of the SSH key you use for creating your applications
+ssh_key: rustici-cc
+
+# Default AMI, which is just a boilerplate Ubuntu 18.04 box
+# If you want to add your own customizations to app servers, create a new Ubuntu box, apply your
+# customizations, create an AMI of it, and then enter that AMI's ID here
+webami: ami-7ad76705
diff --git a/group_vars/cloudfront.yml.template b/group_vars/cloudfront.yml.template
new file mode 100644
index 0000000..0d02870
--- /dev/null
+++ b/group_vars/cloudfront.yml.template
@@ -0,0 +1,29 @@
+---
+
+# Enable the things. Note that if you aren't also using S3 for file storage,
+# CloudFront isn't going to do anything useful for you
+use_cloudfront: false
+
+# You'll use the access key ID when you create signed URLs or signed cookies.
+cloudfront_access_key_id:
+
+# Distribution Domain - the domain name of your CloudFront distribution
+# This should always be the same as {{ ServerName }}, and we're only adding it here
+# to be really explicit and stupidly verbose.
+cloudfront_distro_domain: "{{ ServerName }}"
+
+# Your CloudFront origin access identity
+cloudfront_origin_access_identity:
+
+# Generate your keys via this process:
+# http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html
+# You'll need to run openssl pkcs8 -topk8 -nocrypt -in privatekey.pem -inform PEM -out privatekey.der -outform DER
+# against your private key to make it work with Java.
+# Place your private key and public key in "roles/cloudfront/files"
+cloudfront_private_key: privatekey.der
+cloudfront_public_key: rsa-publickey.pem
+
+# NOTE: If you think you need a local content proxy, please contact
+# Rustici Software support for more information. We'll need to grant
+# you access to the release bucket.
+content_proxy_enabled: false
diff --git a/group_vars/content_controller.yml.template b/group_vars/content_controller.yml.template
new file mode 100644
index 0000000..7aef67d
--- /dev/null
+++ b/group_vars/content_controller.yml.template
@@ -0,0 +1,60 @@
+---
+
+# The cc_db_host var controls the location of all the MySQL databases that we use. If this is a remote host,
+# you'll need to ensure that you've entered the root password for that MySQL server in env.yml
+cc_db_host: localhost
+cc_db_password: 5jc6Fv1CT97Jsl6E
+
+# The username and password for connecting to the Engine API, as set in Engine's configuration.
+# This credential is used for both the client and service connections between Content Controller and Engine.
+# See roles/content-controller/contencontroller.yaml.j2 for usage
+engine_user: ScormEngine
+engine_password: QoUeZh8Eo68JgRIT
+
+# The secret key used to sign API auth tokens. Changing this value will
+# invalidate existing tokens, requiring users to re-authenticate.
+# KEEP THIS VALUE SECRET! AN ATTACKER WITH THIS SECRET KEY WILL BE ABLE TO
+# FORGE API AUTHENTICATION TOKENS!
+secret_key: FFPJiHlnFQxiRLUDPJYL8rAHHhrLNgqKClR60uh6P28W1C9hZoDqrTWfpCrIxyOO
+
+# The deep linking secret key used to sign tokens to and from the ContentSelector App during
+# LTI Deep Linking integrations. Changing this value will invalidate existing tokens.
+# KEEP THIS VALUE SECRET! AN ATTACKER WITH THIS SECRET KEY WILL BE ABLE TO
+# FORGE DEEP LINKING AUTHENTICATION TOKENS!
+deep_linking_secret: uaTtLvpPD0tik7QbJHfzOozkWfC8YJxrP4RQcNcanw2DCkoFGfQrVb6J4flQp05U
+
+# The webhooks auth secret key is used to encrypt the key/secret for the postback requests
+# in our database. Changing this value will invalidate existing credentials.
+# KEEP THIS VALUE SECRET! AN ATTACKER WITH THIS SECRET KEY WILL BE ABLE TO
+# DECRYPT CREDENTIALS TO WEBHOOK TARGET SYSTEMS!
+webhooks_auth_secret: 9kuDQ0fRnSBSLMqd8shtAytGTrXJn0L7vOpsQZGYHSY8NQYIeCyBlEFtt19JIu89
+
+# These settings provision the initial user for the Content Controller web interface.
+initial_cc_users:
+ - username: 'test.user@example.com'
+ full_name: 'Test User'
+ password: 'test^passWord12'
+ email: 'test.user@example.com'
+
+# This setting enables the automation API
+enable_automation_api: false
+
+# Setting to enable customization of the sign-in screen
+enable_signin_customization: true
+
+# Settings to enable Content Controller to send emails (such as license alert emails)
+enable_emails: false # Allow Content Controller to send emails
+cc_smtp_host: localhost # Defaults to localhost (optional)
+cc_smtp_port: 25 # Defaults to 25 (optional)
+cc_smtp_username: user # (optional)
+cc_smtp_password: 'password' # (optional)
+cc_smtp_transport: TLS # One of ['SSL', 'TLS', 'PLAIN'] - Defaults to 'PLAIN' (optional)
+cc_smtp_from_name: 'Content Controller' # Default from name for emails - Defaults to 'Content Controller' (optional)
+cc_smtp_from_email: 'no-reply@example.com' # Default from email address - Defaults to 'no-reply@contentcontroller.com' (optional)
+
+# These configure Content Controller's integration with your SSO provider.
+enable_saml: false
+keystore_password: demo-passwd
+private_key_password: demo-passwd
+maximum_auth_lifetime: 3600
+include_query_param_on_callback: true
diff --git a/group_vars/engine_java.yml.template b/group_vars/engine_java.yml.template
new file mode 100644
index 0000000..d5d2578
--- /dev/null
+++ b/group_vars/engine_java.yml.template
@@ -0,0 +1,22 @@
+---
+
+engine_db_password: s9Iw8GCbe9IlJWwU
+
+console_password: 955ddin3-ca7e-4fx5-8141-a05c4d92f3d1
+
+# This setting can have a powerful effect on database usage in busy environments.
+# It controls how frequently a course calls RecordResults.jsp
+# In milliseconds
+# Default value is 10000
+# Suggest value for a busy environment is 20000
+DefaultCommCommitFrequency: 10000
+
+# The frequency (in milliseconds) the dispatch package will poll back to Engine
+# for an updated registration state. This setting will be used only by non-SCORM
+# content packages.
+#
+# Default value is 1500 milliseconds
+DispatchPollingFrequency: 1500
+
+# Whether or not the `accountServiceHomePage` of actors launching Tin Can courses should be different for each account
+tin_can_account_system_page: true
\ No newline at end of file
diff --git a/group_vars/env.yml.template b/group_vars/env.yml.template
new file mode 100644
index 0000000..d97211e
--- /dev/null
+++ b/group_vars/env.yml.template
@@ -0,0 +1,145 @@
+---
+
+########################
+# Vars you need to set #
+########################
+
+# The env variable determines the environment, as in prod, staging, qa, dev, etc.
+# Note that this var will have an effect on the playbooks' behavior. In particular,
+# choosing "dev" will set up a bunch of stuff that you prolly don't want running
+# on production machines. Choosing prod will prevent (among other things) the
+# creation of a .my.cnf file in /root
+
+# This var has limited effect, and is mostly present so that these playbooks can be extended
+# for multi-deployment environments.
+# If you're just testing stuff, use "dev"
+# If this is a production deployment, use "prod"
+env: dev
+
+# This should be the fully qualified domain name of your content controller server,
+# the name that you'll be hitting in a browser to access it.
+# If you use the self-signed SSL certificates, this is the name that they'll use.
+ServerName: cc.example.com
+
+# This should be the fully qualified domain name of your content portal server,
+# the name that you'll be hitting in a browser to access it.
+# If you use the self-signed SSL certificates, this is the name that they'll use.
+ContentPortalServerName: contentportal.example.com
+
+# Whomever is root - this is mostly for the apache config. Not really important, but there if you want it :-)
+ServerAdmin: root@localhost
+
+# Uncomment this line if you're using RedHat or CentOS 8. Otherwise, delete this line or leave it commented out.
+
+# If you set initalize_mysql to true, we will run the mysql_config role, which sets up users and passwords, and removes
+# default databases and such. Must be used on initial setup of the server(s), but is elective thereafter.
+# During install, we pass this a value of "true" on the command line to initialize the databases and users.
+# This command is safe to run on an existing system, and must be run if you change any of the mysql credentials in ansible
+initialize_mysql: true
+mysql_version: '8.0'
+
+# If you're going to use S3, this must be set to "True"
+S3FileStorageEnabled: False
+
+# Tomcat-Admin users
+# We remove Tomcat-Admin by default, but we install these creds as a precaution in case someone
+# decided to enable it.
+tomcat_user: 'tomcat-admin'
+tomcat_password: t92RjxOU0khdtCVM
+
+# MySQL root password. This setting is only used for the database setup routines.
+# DB creds for Content Controller live in content_controller.yml and engine_java.yml
+# If you have already provisioned a root user for your MySQL database server, you'll
+# want to enter this value here.
+
+# If you're using "localhost" as your MySQL server, and you run the mysql-config role
+# the mysql root password will be set to this value.
+# If you're NOT using localhost as your MySQL server, we will NOT attempt to set the root password in the
+# mysql-config role. We're polite like that :-)
+
+# This value won't change an existing root credential - it only sets it on a brand-new installation.
+
+# We'll put this in a .my.cnf file in /root for your convenience.
+mysql_root_user: root
+mysql_root_password: BcLHIRBd9jTtlvb1
+
+# The root directory where you want content to be stored.
+# For example, if you use /mnt, your content will be stored in /mnt/content
+# No trailing slash please.
+data_root: /mnt
+
+# SSL Configuration
+
+# If you'll be terminating SSL elsewhere (like on a load balancer) set use_ssl to false
+# and allow_80 to true.
+# We don't support running Content Controller without using SSL *somewhere*.
+# You can hack it up and make it go, but please don't unless you absolutely must.
+use_ssl: true
+allow_80: false
+
+# For Apache:
+# These should be set to the files that you want to use
+# That live in the roles/ssl/files folder.
+
+# Set use_self_signed to have ansible generate a self-signed certificate for you
+# which is fine for testing stuff.
+# If it is set to any value other than true, we'll use SSL certificates specified below
+# SELF SIGNED CERT AUTO-GENERATION DOESN'T WORK ON REDHAT BASED DEPLOYMENTS
+# These are for development purposes, and all the dev stuff happens on Ubuntu boxen, so we don't support this
+# feature on RedHat/CentOS
+use_self_signed: true
+
+# To use your own SSL certificates, set the filenames you wish to use here.
+# You'll need to put the certificates themselves in
+# roles/ssl/files, and their filenames must match the values below.
+
+# The SSL Public certificate
+ssl_certificate: contentcontroller.example.com.crt
+
+# Your private key. Guard it carefully :-)
+ssl_key: contentcontroller.example.com.key
+
+# The SSL certificate authority's chain file
+# If you don't need a chain file, then just leave the variable in place, but with no value
+ssl_chain: SomeAuthoritysChainFile.crt
+
+# Add `X-UA-Compatible: IE=edge` Internet Explorer compatibility header. You should leave this disabled
+# unless you know that you need it. It's useful when running CC inside of a corporate intranet where group
+# policies set a lower IE compatibility mode by default.
+set_ie_compatability_mode_header: false
+
+############################
+# End Vars you need to set #
+############################
+
+############################
+# Optional Vars #
+############################
+
+# Java Heap Sizes
+# On production environments, tt is VERY important to set these variable to match your target system!
+# To choose your heap sizes, take the amount of RAM in your system, subtract the OS overhead, and subtract another 50% of the OS overhead.
+# Split the remaining RAM between the engine_heap_size and cc_heap size.
+# So, if you have 8GB RAM in your box, and your OS needs 1GB to run+50%, that leaves you 6.5GB to allocate.
+
+# A good rule of thumb for allocation is that you should give 2/3 of the available memory to Engine,
+# and 1/3 to Content Controller.
+# You'll want to allocate a MINIMUM of 500m to cc_heap size and 1g to the engine_heap_size.
+engine_heap_size: 1g
+cc_heap_size: 500m
+
+# End Apache config vars
+#######################
+
+
+# You'll want to leave this one alone. We include it so that devs can skip checking
+# various time-consuming dependencies when they just want to run a quick update.
+# Nothing to see here, move along.
+skip_deps: false
+
+use_local_apt_cache: false
+local_apt_cache_host: mirror.office.rusticisoftware.com
+
+# Set noupgrade to true to prevent apt from patching the box with each run.
+# Generally, you want it to patch itself unless you have some sort of network policy that prevents access to public apt caches.
+noupgrade: false
diff --git a/group_vars/keypair.yml.template b/group_vars/keypair.yml.template
new file mode 100644
index 0000000..60a8823
--- /dev/null
+++ b/group_vars/keypair.yml.template
@@ -0,0 +1,8 @@
+---
+
+#In order to download releases of Content Controller, you'll need to have a keypair
+#(The folks at Rustici will provide you with a keypair to use)
+
+s3_download_key:
+s3_download_secret:
+use_s3_release: true
diff --git a/group_vars/s3.yml.template b/group_vars/s3.yml.template
new file mode 100644
index 0000000..85d80b9
--- /dev/null
+++ b/group_vars/s3.yml.template
@@ -0,0 +1,48 @@
+---
+
+# Configuration for using Amazon S3 for content storage
+# If you aren't using S3, you can safely ignore this file
+
+# The variable in env.yml called S3FileStorageEnabled must be set to "True" for any of this to have an effect.
+
+########################
+# Vars you need to set #
+########################
+
+# The Account ID in which your bucket lives
+S3AWSAccountID: 2134123412341234
+
+# If you are using S3 for content storage, this should be an AWS Keypair
+# that has read/write access to the bucket where your content lives
+# To generate IAM and bucket policies suitable for your purposes, please see the README
+# and search for "S3 Support"
+S3FileStorageIAMUsername: "contentcontroller-s3user"
+S3FileStorageAwsId: "YourAWS-IAM-API-id"
+S3FileStorageAwsKey: "YourIAM-API-Secret-Key"
+
+# The name of the bucket in which you'll store your content.
+S3FileStorageBucket: "cc-content-storage"
+
+# The S3 region to use.
+S3FileStorageRegion: "us-east-1"
+
+# The S3 endpoint to connect to; should match the region set above, can not contain a custom port
+S3Endpoint: "s3-external-1.amazonaws.com"
+
+# The HTTP port to use for S3 (optional)
+#cc_s3_http_port: 80
+
+# The HTTPS port to use for S3 (optional)
+#cc_s3_https_port: 443
+
+# Force HTTPS for S3 (optional, default `true`)
+#cc_s3_https_only: true
+
+# Disable DNS buckets (see http://www.jets3t.org/toolkit/configuration.html for more info)
+#cc_s3_disable_dns_buckets: false
+
+############################
+# End Vars you need to set #
+############################
+
+S3FileStorageTempDir: "{{ data_root }}/s3temp"
diff --git a/host_vars/README.md b/host_vars/README.md
new file mode 100644
index 0000000..abc8c8d
--- /dev/null
+++ b/host_vars/README.md
@@ -0,0 +1,3 @@
+# host_vars usage
+
+vars files in this directory are per-host. When you make a vars file for a host, it should only contain those vars that differ from what's in `group_vars`
diff --git a/host_vars/example.contentcontroller.net.yml b/host_vars/example.contentcontroller.net.yml
new file mode 100644
index 0000000..62059e7
--- /dev/null
+++ b/host_vars/example.contentcontroller.net.yml
@@ -0,0 +1,10 @@
+ServerName: "example.contentcontroller.net"
+
+tomcat_password: 'redacted password'
+mysql_root_password: 'redacted password'
+cc_db_password: 'redacted password'
+engine_db_password: 'redacted password'
+mysql_root_password: 'redacted password'
+
+# Initial user's password for the Content Controller web interface.
+cc_user_password: 'redacted password'
\ No newline at end of file
diff --git a/host_vars/localhost b/host_vars/localhost
new file mode 100644
index 0000000..74b3904
--- /dev/null
+++ b/host_vars/localhost
@@ -0,0 +1,2 @@
+ansible_python_interpreter: /usr/bin/env python
+ansible_connection: local
diff --git a/img/Rustici_ContentController.png b/img/Rustici_ContentController.png
new file mode 100644
index 0000000..11316eb
Binary files /dev/null and b/img/Rustici_ContentController.png differ
diff --git a/inventory b/inventory
new file mode 100644
index 0000000..1614642
--- /dev/null
+++ b/inventory
@@ -0,0 +1,14 @@
+# This is an example inventory file. If you're setting up servers once and deployment to them every time,
+# you can use the inventory file to avoid needing to specify every server at the command line.
+#
+# To use, copy this file with a new name (such as inventory.prod or inventory.stg), then when running your
+# playbooks, add it from the command line:
+# ansible-playbook -i inventory.prod ....
+#
+# For more information, see http://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
+
+[contentcontroller]
+10.1.1.1
+10.1.1.2
+10.1.1.3
+
diff --git a/link-vars-dev.sh b/link-vars-dev.sh
new file mode 100755
index 0000000..8b69d10
--- /dev/null
+++ b/link-vars-dev.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+if [ ! -d ../cc-deployment-dev ]; then
+ echo "Please clone cc-deployment-dev to the same directory as cc-deployment-public before running this script."
+ exit 1
+fi
+
+# Link group vars
+cd group_vars
+for file in `find ../../cc-deployment-dev/group_vars -name '*.yml' -type f -exec basename {} ';'`; do
+ ln -fs "../../cc-deployment-dev/group_vars/$file" "$file"
+done
+cd ..
+
+# Link host vars
+cd host_vars
+for file in `find ../../cc-deployment-dev/host_vars -name '*.yml' -type f -exec basename {} ';'`; do
+ ln -fs "../../cc-deployment-dev/host_vars/$file" "$file"
+done
+cd ..
+
+# Link ansible.cfg
+ln -s ../cc-deployment-dev/ansible.cfg ./
+
+# Link SSL certs
+cd roles/ssl
+ln -fs ../../../cc-deployment-dev/roles/ssl/files files
+cd ../..
+
+# Link roles
+cd roles
+ln -fs ../../cc-deployment-dev/roles/minio .
+ln -fs ../../cc-deployment-dev/roles/qa-dev .
+cd ..
+
+# Link .ymls
+for file in `find ../cc-deployment-dev -name '*.yml' -maxdepth 1 -type f -exec basename {} ';'`; do
+ ln -fs "../cc-deployment-dev/$file" "$file"
+done
diff --git a/link-vars-private.sh b/link-vars-private.sh
new file mode 100755
index 0000000..50b418d
--- /dev/null
+++ b/link-vars-private.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+
+if [ ! -d ../cc-deployment-private ]; then
+ echo "Please clone cc-deployment-private to the same directory as cc-deployment-public before running this script."
+ exit 1
+fi
+
+cd group_vars
+if [ -a ../../cc-deployment-private/group_vars/aws.yml ]; then
+ ln -fs ../../cc-deployment-private/group_vars/aws.yml aws.yml
+fi
+
+if [ -a ../../cc-deployment-private/group_vars/cloudfront.yml ]; then
+ ln -fs ../../cc-deployment-private/group_vars/cloudfront.yml cloudfront.yml
+fi
+
+if [ -a ../../cc-deployment-private/group_vars/content_controller.yml ]; then
+ ln -fs ../../cc-deployment-private/group_vars/content_controller.yml content_controller.yml
+fi
+
+if [ -a ../../cc-deployment-private/group_vars/engine_java.yml ]; then
+ ln -fs ../../cc-deployment-private/group_vars/engine_java.yml engine_java.yml
+fi
+
+if [ -a ../../cc-deployment-private/group_vars/env.yml ]; then
+ ln -fs ../../cc-deployment-private/group_vars/env.yml env.yml
+fi
+
+if [ -a ../../cc-deployment-private/group_vars/s3.yml ]; then
+ ln -fs ../../cc-deployment-private/group_vars/s3.yml s3.yml
+fi
+
+if [ -a ../../cc-deployment-private/group_vars/keypair.yml ]; then
+ ln -fs ../../cc-deployment-private/group_vars/keypair.yml keypair.yml
+fi
+
+cd ../host_vars
+
+cp ../../cc-deployment-private/host_vars/* .
+
+cd ../roles
+
+if [ -d ../../cc-deployment-private/roles/users ]; then
+ ln -fs ../../cc-deployment-private/roles/users .
+fi
+
+cp -rp ../../cc-deployment-private/roles/ssl/files ssl/files
+
+cp -rp ../../cc-deployment-private/roles/cloudfront/files cloudfront/files
+
+if [ -d ../../cc-deployment-private/roles/logstash-filebeat ]; then
+ ln -fs ../../cc-deployment-private/roles/logstash-filebeat .
+fi
+
+if [ -d ../../cc-deployment-private/roles/dripstat ]; then
+ ln -fs ../../cc-deployment-private/roles/dripstat .
+fi
+
+if [ -d ../../cc-deployment-private/roles/site24x7 ]; then
+ ln -fs ../../cc-deployment-private/roles/site24x7 .
+fi
+
+if [ -d ../../cc-deployment-private/roles/scorm-engine-2016 ]; then
+ ln -fs ../../cc-deployment-private/roles/scorm-engine-2016 .
+fi
+
+if [ -d ../../cc-deployment-private/roles/apache-engine-2016 ]; then
+ ln -fs ../../cc-deployment-private/roles/apache-engine-2016 .
+fi
+
+if [ -d ../../cc-deployment-private/roles/doc ]; then
+ ln -fs ../../cc-deployment-private/roles/doc .
+fi
+
+if [ -d ../../cc-deployment-private/roles/mocha-api ]; then
+ ln -fs ../../cc-deployment-private/roles/mocha-api .
+fi
+
+if [ -d ../../cc-deployment-private/roles/consul ]; then
+ ln -fs ../../cc-deployment-private/roles/consul .
+fi
+
+if [ -d ../../cc-deployment-private/roles/newrelic-infrastructure/ ]; then
+ ln -fs ../../cc-deployment-private/roles/newrelic-infrastructure .
+fi
+
+cd ../
+
+if [ -a ../cc-deployment-private/engine.yml ]; then
+ ln -fs private/engine.yml engine.yml
+fi
+
+if [ -a ../cc-deployment-private/build_ami.yml ]; then
+ rm -f build_ami.yml
+ ln -fs private/build_ami.yml build_ami.yml
+fi
+
+if [ -a ../cc-deployment-private/build_ami_engine2016.yml ]; then
+ ln -fs private/build_ami_engine2016.yml build_ami_engine2016.yml
+fi
diff --git a/roles/apache/defaults/main.yml b/roles/apache/defaults/main.yml
new file mode 100644
index 0000000..7909d1b
--- /dev/null
+++ b/roles/apache/defaults/main.yml
@@ -0,0 +1,42 @@
+---
+
+# Whomever is root - this is mostly for the apache config. Not really important, but there if you want it :-)
+ServerAdmin: root@localhost
+
+# Allows apache to listen for requests on port 80
+allow_80: false
+
+# When allow_80 is set to true for insecure LMSs, this forces all non-LMS related requests to be HTTPS.
+# This should always be true and HTTPS should be used somewhere (either here in apache or in a load balancer).
+apache_force_non_lms_https: true
+
+# Security settings (don't change these unless you know what you're doing)
+use_hsts: false
+hsts_max_age: 15768000 # 6 months
+hsts_include_subdomains: false
+ssl_cipher_suite: "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
+ssl_protocol: "all -SSLv2 -SSLv3"
+application_x_frame_options: "deny" # For IE 11 support
+application_content_security_policy: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' unpkg.com; style-src 'self' 'unsafe-inline' cdn.jsdelivr.net; connect-src 'self' unpkg.com; frame-src 'self'; frame-ancestors 'none'; img-src 'self' data:; style-src-elem 'self' cdn.jsdelivr.net 'unsafe-inline'; script-src-elem 'self' unpkg.com 'unsafe-inline'; font-src 'self' cdn.jsdelivr.net;"
+
+# Defined, but null by default
+ssl_chain:
+
+MaxRequestWorkers: 150
+
+html_path: "/var/www/html/"
+
+# Content Portal HTML path on the VM
+cp_html_path: "/var/www/html/contentportal/"
+
+tomcat_base: "/usr/share/tomcat"
+
+set_ie_compatability_mode_header: false
+
+# Set apache state when configuration changes are made. Recommended values:
+# `restarted` or `reloaded`
+apache_restart_state: restarted
+
+# Apache package state; use `present` to make sure it's installed, or `latest` if
+# you want to upgrade or switch versions using a new repo.
+apache_packages_state: present
diff --git a/roles/apache/files/contentcontroller.pp b/roles/apache/files/contentcontroller.pp
new file mode 100644
index 0000000..c7bca8c
Binary files /dev/null and b/roles/apache/files/contentcontroller.pp differ
diff --git a/roles/apache/files/contentcontroller.te b/roles/apache/files/contentcontroller.te
new file mode 100644
index 0000000..0bfe60b
--- /dev/null
+++ b/roles/apache/files/contentcontroller.te
@@ -0,0 +1,14 @@
+
+module contentcontroller 1.0;
+
+require {
+ type init_t;
+ type var_lib_t;
+ class file { execute execute_no_trans };
+}
+
+#============= init_t ==============
+
+#!!!! This avc is allowed in the current policy
+allow init_t var_lib_t:file execute;
+allow init_t var_lib_t:file execute_no_trans;
diff --git a/roles/apache/files/tomcat-connectors-1.2.46-src.tar.gz b/roles/apache/files/tomcat-connectors-1.2.46-src.tar.gz
new file mode 100644
index 0000000..94a2495
Binary files /dev/null and b/roles/apache/files/tomcat-connectors-1.2.46-src.tar.gz differ
diff --git a/roles/apache/handlers/main.yml b/roles/apache/handlers/main.yml
new file mode 100644
index 0000000..52b9baf
--- /dev/null
+++ b/roles/apache/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: restart httpd
+ service:
+ name: "{{ apache_service }}"
+ state: "{{ apache_restart_state }}"
diff --git a/roles/apache/meta/main.yml b/roles/apache/meta/main.yml
new file mode 100644
index 0000000..c46b2a3
--- /dev/null
+++ b/roles/apache/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+ - { role: ssl, when: not skip_deps and not use_ssl }
+ - { role: java }
diff --git a/roles/apache/tasks/configure-Debian.yml b/roles/apache/tasks/configure-Debian.yml
new file mode 100644
index 0000000..07a4ec0
--- /dev/null
+++ b/roles/apache/tasks/configure-Debian.yml
@@ -0,0 +1,22 @@
+---
+- name: Enable HTTP Virtual Host
+ command: a2ensite 000-default.conf
+ notify: restart httpd
+
+- name: Enable HTTPS Virtual Host
+ command: a2ensite 000-default-ssl.conf
+ notify: restart httpd
+ when: use_ssl
+
+- name: Disable mpm_event mod
+ command: a2dismod mpm_event
+ notify: restart httpd
+
+- name: Enable required mods
+ command: a2enmod rewrite ssl proxy proxy_http headers jk mpm_prefork expires
+ notify: restart httpd
+
+- name: Install Logrotate file
+ template:
+ src: apache2.logrotate
+ dest: /etc/logrotate.d/apache.logrotate
diff --git a/roles/apache/tasks/configure-RedHat.yml b/roles/apache/tasks/configure-RedHat.yml
new file mode 100644
index 0000000..cd21505
--- /dev/null
+++ b/roles/apache/tasks/configure-RedHat.yml
@@ -0,0 +1,2 @@
+---
+
diff --git a/roles/apache/tasks/main.yml b/roles/apache/tasks/main.yml
new file mode 100644
index 0000000..77cae46
--- /dev/null
+++ b/roles/apache/tasks/main.yml
@@ -0,0 +1,67 @@
+---
+- name: Stop NGINX if it is running
+ service: name=nginx state=stopped
+ ignore_errors: true
+
+- name: Include OS-specific variables
+ include_vars: "{{ ansible_os_family }}.yml"
+
+- name: Define packages to install
+ set_fact:
+ apache_packages: "{{ __apache_packages | list }}"
+ when: apache_packages is not defined
+
+- include_tasks: "setup-{{ ansible_os_family }}.yml"
+
+- name: Ensure that libapache2-mod-jk directory exists
+ file:
+ state: directory
+ path: /etc/libapache2-mod-jk
+ owner: root
+ group: root
+ mode: 0755
+
+- name: Install config files
+ template:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ mode: 0644
+ with_items:
+ - { src: "max_clients.conf.j2", dest: "{{ apache_conf_path }}/max_clients.conf" }
+ - { src: "security.conf.j2", dest: "{{ apache_conf_path }}/security.conf" }
+ - { src: "jk.conf.j2", dest: "{{ apache_mods_path }}/jk.conf" }
+ - { src: "mpm_prefork.conf.j2", dest: "{{ apache_mods_path }}/mpm_prefork.conf" }
+ - { src: "ssl.conf.j2", dest: "{{ apache_mods_path }}/ssl.conf" }
+ - { src: "workers.properties.j2", dest: "/etc/libapache2-mod-jk/workers.properties" }
+ notify: restart httpd
+
+- name: Install HTTP VirtualHost config file
+ template:
+ src: 000-default.conf.j2
+ dest: "{{ apache_sites_path }}/000-default.conf"
+ mode: 0644
+ notify: restart httpd
+
+- name: Install HTTPS VirtualHost config file
+ template:
+ src: 000-default-ssl.conf.j2
+ dest: "{{ apache_sites_path }}/000-default-ssl.conf"
+ mode: 0644
+ notify: restart httpd
+ when: use_ssl
+
+- include_tasks: "configure-{{ ansible_os_family }}.yml"
+
+- name: Install robots.txt
+ template:
+ src: robots.txt
+ dest: "{{ html_path }}/robots.txt"
+ owner: root
+ group: root
+ mode: 0644
+
+- name: Ensure Apache has been started and is enabled on boot
+ service:
+ name: "{{ apache_service }}"
+ state: started
+ enabled: yes
diff --git a/roles/apache/tasks/setup-Debian.yml b/roles/apache/tasks/setup-Debian.yml
new file mode 100644
index 0000000..8d87c9e
--- /dev/null
+++ b/roles/apache/tasks/setup-Debian.yml
@@ -0,0 +1,18 @@
+---
+- name: Update apt cache
+ apt: update_cache=yes cache_valid_time=3600
+
+- name: Install Apache
+ apt: "name={{ item }} state={{ apache_packages_state }}"
+ with_items: "{{ apache_packages }}"
+
+- name: Disable all Virtual Hosts
+ shell: rm -f {{ apache_server_root }}/sites-enabled/*
+ ignore_errors: yes
+
+- name: Install config files - apache2.conf
+ template:
+ src: Debian-apache2.conf.j2
+ dest: "{{ apache_server_root }}/apache2.conf"
+ mode: 0644
+ notify: restart httpd
diff --git a/roles/apache/tasks/setup-RedHat.yml b/roles/apache/tasks/setup-RedHat.yml
new file mode 100644
index 0000000..9e2a476
--- /dev/null
+++ b/roles/apache/tasks/setup-RedHat.yml
@@ -0,0 +1,65 @@
+---
+- name: Install Apache
+ yum:
+ name: "{{ item }}"
+ state: "{{ apache_packages_state }}"
+ with_items: "{{ apache_packages }}"
+
+- name: Remove default config files
+ shell: rm -f {{ apache_server_root }}/conf.d/*
+ ignore_errors: yes
+
+- name: Install config files - httpd.conf
+ template:
+ src: RedHat-httpd.conf.j2
+ dest: "{{ apache_server_root }}/conf/{{ apache_daemon }}.conf"
+ mode: 0644
+ notify: restart httpd
+
+- name: Create target dir for mod_jk
+ file:
+ state: directory
+ dest: /opt/mod_jk
+ owner: root
+ group: root
+ mode: 0755
+
+- name: Copy over mod_jk installer
+ unarchive:
+ src: tomcat-connectors-1.2.46-src.tar.gz
+ dest: /opt/mod_jk
+
+- name: Copy over mod_jk compile script
+ template:
+ src: install_mod_jk.sh
+ dest: /usr/local/bin/install_mod_jk.sh
+ owner: root
+ group: root
+ mode: 0750
+
+- name: Compile mod_jk
+ command: /usr/local/bin/install_mod_jk.sh
+
+- name: "Enabling SELinux booleans"
+ shell: "/usr/sbin/setsebool -P {{ item }} 1"
+ with_items:
+ - httpd_can_network_connect
+ - domain_can_mmap_files
+ - httpd_use_nfs
+ ignore_errors: true
+
+- name: "Copy SELinux policy files"
+ copy:
+ src: "{{ item }}"
+ dest: /etc/selinux/targeted/policy/
+ owner: tomcat
+ group: tomcat
+ mode: 0644
+ with_items:
+ - contentcontroller.pp
+ - contentcontroller.te
+ when: ansible_distribution_major_version == "8"
+
+- name: "Apply policy files"
+ shell: "semodule -d contentcontroller; semodule -B; semodule -i /etc/selinux/targeted/policy/contentcontroller.pp"
+ when: ansible_distribution_major_version == "8"
\ No newline at end of file
diff --git a/roles/apache/templates/000-default-ssl.conf.j2 b/roles/apache/templates/000-default-ssl.conf.j2
new file mode 100644
index 0000000..14db2de
--- /dev/null
+++ b/roles/apache/templates/000-default-ssl.conf.j2
@@ -0,0 +1,205 @@
+{% if ContentPortalServerName is defined %}
+
+ ServerName {{ ContentPortalServerName }}
+ DocumentRoot {{ cp_html_path }}
+
+ SSLEngine on
+ SSLCertificateFile /etc/ssl/certs/{{ cp_ssl_certificate }}
+ SSLCertificateKeyFile /etc/ssl/private/{{ cp_ssl_key }}
+
+{% endif %}
+
+
+
+ ServerName {{ ServerName }}
+ ServerAdmin {{ ServerAdmin }}
+ {% if ServerAliases is defined %}
+ {% for alias in ServerAliases %}
+ ServerAlias {{ alias }}
+ {% endfor %}
+ {% endif %}
+ ServerSignature Off
+ Options -Indexes
+
+ SSLEngine on
+ SSLProxyEngine On
+ SSLCertificateFile /etc/ssl/certs/{{ ssl_certificate }}
+ SSLCertificateKeyFile /etc/ssl/private/{{ ssl_key }}
+
+ {% if not use_self_signed and ssl_chain != None %}
+ SSLCertificateChainFile /etc/ssl/certs/{{ ssl_chain }}
+ {% endif %}
+
+ Header set X-XSS-Protection: "1; mode=block"
+
+ # IE won't let us set cookies without this.
+ Header set P3P 'CP="NOI"'
+
+ # Required for IE subtitle/caption support
+
+ AddType text/vtt .vtt
+
+
+ {% if use_hsts and not allow_80 %}
+ # HSTS (mod_headers is required)
+ Header always set Strict-Transport-Security "max-age={{ hsts_max_age }}{% if hsts_include_subdomains %}; includeSubDomains{% endif %}"
+ {% endif %}
+
+ RewriteEngine on
+
+ # Rewrite all URLS to point at the primary
+ {% if ServerAliases is defined %}
+ {% for alias in ServerAliases %}
+ RewriteCond "%{HTTP_HOST}" "{{ alias }}" [NC]
+ RewriteRule "^/?$" "https://{{ ServerName }}/$1" [L,R,NE]
+ {% endfor %}
+ {% endif %}
+
+ #Block access to .git directories
+ RewriteRule ^(.*\/)?\.git - [R=404,L]
+
+ RewriteRule \.ini$ - [R=404,L]
+
+ ##Set max header size to 16Kb
+ ##raise limit to 16Kb to support large cookies created by lectora courses
+ LimitRequestFieldSize 16384
+
+ SetEnvIf Request_URI "^/ScormEngineInterface/dispatch/.*\.(js|html)$" OVERRIDE_DISPATCH_CACHE_HEADERS=TRUE
+ Header add 'Cache-Control' 'no-cache, must-revalidate' env=OVERRIDE_DISPATCH_CACHE_HEADERS
+
+ SetEnvIf Request_URI "^(/(#/.*)?)?$" ROOT_REQUEST=TRUE
+ {% if application_x_frame_options|length > 0 %}
+ Header add 'X-Frame-Options' "{{ application_x_frame_options }}" env=ROOT_REQUEST
+ {% endif %}
+
+ {% if application_content_security_policy|length > 0 %}
+ Header add 'Content-Security-Policy' "{{ application_content_security_policy }}" env=ROOT_REQUEST
+ {% endif %}
+
+ JkMount /ScormEngineInterface/* ajp13_worker
+
+ ProxyPass /api/ http://localhost:8989/api/
+ ProxyPassReverse /api http://localhost:8989/api/
+
+{% if content_proxy_enabled %}
+{% if enable_contentvault %}
+ ProxyPass /vault/ http://localhost:8090/vault/
+ ProxyPassReverse /vault http://localhost:8090/vault/
+{% else %}
+ ProxyPass /courses/ http://localhost:8090/courses/
+ ProxyPassReverse /courses http://localhost:8090/courses/
+{% endif %}
+{% endif %}
+
+
+ Order allow,deny
+ Allow from all
+{% if cors_allow_origins_for_all is defined and cors_allow_origins_for_all %}
+ Header set 'Access-Control-Allow-Origin' '*'
+ Header add 'Access-Control-Allow-Credentials' 'true'
+ Header add 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'
+ Header add 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,EngineDispatchId,EngineDispatchLaunchSecret'
+{% endif %}
+ # If an endpoint has specified its own caching behavior, we should respect it (and if the OS supports a version of Apache that allows setIfEmpty)
+{% if ansible_os_family == 'Debian' %}
+ Header setIfEmpty 'Cache-Control' 'no-cache, no-store, must-revalidate'
+ Header setIfEmpty 'Expires' 0
+{% else %}
+ Header add 'Cache-Control' 'no-cache, no-store, must-revalidate'
+ Header add 'Expires' 0
+{% endif %}
+ #RequestHeader set X-Apache-Proxy true
+ #RequestHeader set Host "%{HTTP_HOST}"
+ #RequestHeader set X-Real-IP "%{REMOTE_ADDR}"
+ RequestHeader set X-Forwarded-Proto "https" env=HTTPS
+
+ # We don't want to add the Pragma header on this endpoint because we want it to be cached by CloudFront
+ SetEnvIf Request_URI /api/launch/contentvault/verify/ verify
+ Header always add 'Pragma' 'no-cache' env=!verify
+
+ SetEnvIf Request_URI /api/launch/ ExternalAccess
+ SetEnvIf Request_URI /api/rxd/results ExternalAccess
+ SetEnvIf Request_URI /api/sdxd/results ExternalAccess
+
+ Header set "Access-Control-Allow-Origin" "*" env=ExternalAccess
+ Header add 'Access-Control-Allow-Credentials' 'true' env=ExternalAccess
+ Header add 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' env=ExternalAccess
+ Header add 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,EngineDispatchId,EngineDispatchLaunchSecret' env=ExternalAccess
+
+
+ ProxyPass /healthcheck http://localhost:8981/healthcheck
+ ProxyPassReverse /healthcheck http://localhost:8981/healthcheck
+
+
+ Order allow,deny
+ Allow from all
+ Header add 'Cache-Control' 'no-cache, no-store, must-revalidate'
+ Header add 'Pragma' 'no-cache'
+ Header add 'Expires' 0
+ RequestHeader set X-Forwarded-Proto "https" env=HTTPS
+
+
+ ProxyPass /ping http://localhost:8981/ping
+ ProxyPassReverse /ping http://localhost:8981/ping
+
+
+ Order allow,deny
+ Allow from all
+ Header add 'Cache-Control' 'no-cache, no-store, must-revalidate'
+ Header add 'Pragma' 'no-cache'
+ Header add 'Expires' 0
+ RequestHeader set X-Forwarded-Proto "https" env=HTTPS
+
+
+
+ # Manually set header because the Tomcat CORS filter won't let us use it with Access-Control-Allow-Origin: *
+ Header set Access-Control-Allow-Credentials: "true"
+
+
+ {% if env == 'dev' %}
+ ProxyPass /minio http://localhost:9000/minio
+ ProxyPassReverse /minio http://localhost:9000/minio
+
+
+ Order allow,deny
+ Allow from all
+ Header add 'Access-Control-Allow-Origin' '*'
+ Header add 'Access-Control-Allow-Credentials' 'true'
+ Header add 'Access-Control-Allow-Methods' 'GET'
+ Header add 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,EngineDispatchId,EngineDispatchLaunchSecret'
+ RequestHeader set X-Forwarded-Proto "https" env=HTTPS
+
+ {% endif %}
+
+ DocumentRoot {{ html_path }}
+
+
+ Options FollowSymLinks MultiViews
+ AllowOverride None
+ Order allow,deny
+ allow from all
+
+
+ ExpiresActive On
+ ExpiresDefault A1
+ Header append Cache-Control must-revalidate
+{% if set_ie_compatability_mode_header %}
+ Header append X-UA-Compatible IE=edge
+{% endif %}
+
+
+
+
+ ErrorLog {{ apache_logs }}/error.log
+
+ # Possible values include: debug, info, notice, warn, error, crit,
+ # alert, emerg.
+ LogLevel warn
+
+ SetEnvIf REMOTE_ADDR "(.+)" CLIENTIP=$1
+ SetEnvIf X-Forwarded-For "^([0-9.]+)" CLIENTIP=$1
+ LogFormat "%{CLIENTIP}e %l %u %t %D \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" trueip_combined
+
+ CustomLog {{ apache_logs }}/ssl_access.log trueip_combined
+
+
diff --git a/roles/apache/templates/000-default.conf.j2 b/roles/apache/templates/000-default.conf.j2
new file mode 100644
index 0000000..2b7cc72
--- /dev/null
+++ b/roles/apache/templates/000-default.conf.j2
@@ -0,0 +1,205 @@
+{% if use_ssl and not allow_80 %}
+
+
+ RewriteEngine On
+ RewriteCond %{HTTPS} off
+ RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [NE]
+
+
+{% elif allow_80 %}
+
+
+ # Redirect to HTTPS before Apache mod_dir DirectorySlash redirect to HTTP
+ RewriteCond %{HTTP:X-Forwarded-Proto} =https
+ RewriteCond %{LA-U:REQUEST_FILENAME} -d
+ RewriteRule ^/(.*[^/])$ https://%{HTTP_HOST}/$1/ [R=301,L,QSA]
+
+ # Required for IE subtitle/caption support
+
+ AddType text/vtt .vtt
+
+
+ ServerName {{ ServerName }}
+ ServerAdmin {{ ServerAdmin }}
+ {% if ServerAliases is defined %}
+ {% for alias in ServerAliases %}
+ ServerAlias {{ alias }}
+ {% endfor %}
+ {% endif %}
+ ServerSignature Off
+ Options -Indexes
+
+ Header set X-XSS-Protection: "1; mode=block"
+
+ # IE won't let us set cookies without this.
+ Header set P3P 'CP="NOI"'
+
+ RewriteEngine on
+
+ {% if ServerAliases is defined %}
+ # Rewrite all URLS to point at the primary
+ {% for alias in ServerAliases %}
+ RewriteCond "%{HTTP_HOST}" "{{ alias }}" [NC]
+ RewriteRule "^/?$" "https://{{ ServerName }}/$1" [L,R,NE]
+ {% endfor %}
+ {% endif %}
+
+{% if apache_force_non_lms_https %}
+ # Force HTTPS for UI access - HTTP is only for learners from non HTTPS LMS and healthchecks
+ RewriteCond %{HTTP:X-Forwarded-Proto} !https
+ RewriteCond "%{REQUEST_URI}" !^/(ScormEngineInterface|RusticiEngine|healthcheck|ping|courses|api/(launch|sdxd))
+ RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=302,NE,L]
+
+ {% if use_hsts %}
+ SetEnvIf X-Forwarded-Proto "https" HTTPS_PROXY=TRUE
+ Header add Strict-Transport-Security "max-age={{ hsts_max_age }}" env=HTTPS_PROXY
+ {% endif %}
+{% endif %}
+
+ #Block access to .git directories
+ RewriteRule ^(.*\/)?\.git - [R=404,L]
+
+ RewriteRule \.ini$ - [R=404,L]
+
+ ##Set max header size to 16Kb
+ ##raise limit to 16Kb to support large cookies created by lectora courses
+ LimitRequestFieldSize 16384
+
+ SetEnvIf Request_URI "^/ScormEngineInterface/dispatch/.*\.(js|html)$" OVERRIDE_DISPATCH_CACHE_HEADERS=TRUE
+ Header add 'Cache-Control' 'no-cache, must-revalidate' env=OVERRIDE_DISPATCH_CACHE_HEADERS
+
+ SetEnvIf Request_URI "^(/(#/.*)?)?$" ROOT_REQUEST=TRUE
+ {% if application_x_frame_options|length > 0 %}
+ Header add 'X-Frame-Options' "{{ application_x_frame_options }}" env=ROOT_REQUEST
+ {% endif %}
+
+ {% if application_content_security_policy|length > 0 %}
+ Header add 'Content-Security-Policy' "{{ application_content_security_policy }}" env=ROOT_REQUEST
+ {% endif %}
+
+ JkMount /ScormEngineInterface/* ajp13_worker
+
+ ProxyPreserveHost On
+
+{% if content_proxy_enabled %}
+{% if enable_contentvault %}
+ ProxyPass /vault/ http://localhost:8090/vault/
+ ProxyPassReverse /vault http://localhost:8090/vault/
+{% else %}
+ ProxyPass /courses/ http://localhost:8090/courses/
+ ProxyPassReverse /courses http://localhost:8090/courses/
+{% endif %}
+{% endif %}
+
+ ProxyPass /api/ http://localhost:8989/api/
+ ProxyPassReverse /api http://localhost:8989/api/
+
+
+ Order allow,deny
+ Allow from all
+{% if cors_allow_origins_for_all is defined and cors_allow_origins_for_all %}
+ Header set 'Access-Control-Allow-Origin' '*'
+ Header add 'Access-Control-Allow-Credentials' 'true'
+ Header add 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'
+ Header add 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,EngineDispatchId,EngineDispatchLaunchSecret'
+{% endif %}
+
+ {% if ansible_os_family == 'Debian' %}
+ Header setIfEmpty 'Cache-Control' 'no-cache, no-store, must-revalidate'
+ Header setIfEmpty 'Expires' 0
+ {% else %}
+ Header add 'Cache-Control' 'no-cache, no-store, must-revalidate'
+ Header add 'Expires' 0
+ {% endif %}
+
+ # We don't want to add the Pragma header on this endpoint because we want it to be cached by CloudFront
+ SetEnvIf Request_URI /api/launch/contentvault/verify/ verify
+ Header always add 'Pragma' 'no-cache' env=!verify
+
+ SetEnvIf Request_URI /api/launch/ ExternalAccess
+ SetEnvIf Request_URI /api/rxd/results ExternalAccess
+ SetEnvIf Request_URI /api/sdxd/results ExternalAccess
+
+ Header set "Access-Control-Allow-Origin" "*" env=ExternalAccess
+ Header add 'Access-Control-Allow-Credentials' 'true' env=ExternalAccess
+ Header add 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' env=ExternalAccess
+ Header add 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,EngineDispatchId,EngineDispatchLaunchSecret' env=ExternalAccess
+
+
+ ProxyPass /healthcheck http://localhost:8981/healthcheck
+ ProxyPassReverse /healthcheck http://localhost:8981/healthcheck
+
+
+ Order allow,deny
+ Allow from all
+ Header add 'Cache-Control' 'no-cache, no-store, must-revalidate'
+ Header add 'Pragma' 'no-cache'
+ Header add 'Expires' 0
+
+
+ ProxyPass /ping http://localhost:8981/ping
+ ProxyPassReverse /ping http://localhost:8981/ping
+
+
+ Order allow,deny
+ Allow from all
+ Header add 'Cache-Control' 'no-cache, no-store, must-revalidate'
+ Header add 'Pragma' 'no-cache'
+ Header add 'Expires' 0
+ RequestHeader set X-Forwarded-Proto "https" env=HTTPS
+
+
+
+ # Manually set header because the Tomcat CORS filter won't let us use it with Access-Control-Allow-Origin: *
+ Header set Access-Control-Allow-Credentials: "true"
+
+
+ {% if env == 'dev' %}
+ ProxyPass /minio http://localhost:9000/minio
+ ProxyPassReverse /minio http://localhost:9000/minio
+
+
+ Order allow,deny
+ Allow from all
+ Header add 'Access-Control-Allow-Origin' '*'
+ Header add 'Access-Control-Allow-Credentials' 'true'
+ Header add 'Access-Control-Allow-Methods' 'GET'
+ Header add 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,EngineDispatchId,EngineDispatchLaunchSecret'
+ RequestHeader set X-Forwarded-Proto "https" env=HTTPS
+
+ {% endif %}
+
+ DocumentRoot {{ html_path }}
+
+
+ Options FollowSymLinks MultiViews
+ AllowOverride None
+ Order allow,deny
+ allow from all
+
+
+ ExpiresActive On
+ ExpiresDefault A1
+ Header append Cache-Control must-revalidate
+{% if set_ie_compatability_mode_header %}
+ Header append X-UA-Compatible IE=edge
+{% endif %}
+
+
+
+
+ ErrorLog {{ apache_logs }}/error.log
+
+ # Possible values include: debug, info, notice, warn, error, crit,
+ # alert, emerg.
+ LogLevel warn
+
+ SetEnvIf REMOTE_ADDR "(.+)" CLIENTIP=$1
+ SetEnvIf X-Forwarded-For "^([0-9.]+)" CLIENTIP=$1
+ LogFormat "%{CLIENTIP}e %l %u %t %D \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" trueip_combined
+
+ CustomLog {{ apache_logs }}/access.log trueip_combined
+
+
+
+{% endif %}
diff --git a/roles/apache/templates/Debian-apache2.conf.j2 b/roles/apache/templates/Debian-apache2.conf.j2
new file mode 100644
index 0000000..42c666e
--- /dev/null
+++ b/roles/apache/templates/Debian-apache2.conf.j2
@@ -0,0 +1,220 @@
+# This is the main Apache server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See http://httpd.apache.org/docs/2.4/ for detailed information about
+# the directives and /usr/share/doc/apache2/README.Debian about Debian specific
+# hints.
+#
+#
+# Summary of how the Apache 2 configuration works in Debian:
+# The Apache 2 web server configuration in Debian is quite different to
+# upstream's suggested way to configure the web server. This is because Debian's
+# default Apache2 installation attempts to make adding and removing modules,
+# virtual hosts, and extra configuration directives as flexible as possible, in
+# order to make automating the changes and administering the server as easy as
+# possible.
+
+# It is split into several files forming the configuration hierarchy outlined
+# below, all located in the /etc/apache2/ directory:
+#
+# /etc/apache2/
+# |-- apache2.conf
+# | `-- ports.conf
+# |-- mods-enabled
+# | |-- *.load
+# | `-- *.conf
+# |-- conf-enabled
+# | `-- *.conf
+# `-- sites-enabled
+# `-- *.conf
+#
+#
+# * apache2.conf is the main configuration file (this file). It puts the pieces
+# together by including all remaining configuration files when starting up the
+# web server.
+#
+# * ports.conf is always included from the main configuration file. It is
+# supposed to determine listening ports for incoming connections which can be
+# customized anytime.
+#
+# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/
+# directories contain particular configuration snippets which manage modules,
+# global configuration fragments, or virtual host configurations,
+# respectively.
+#
+# They are activated by symlinking available configuration files from their
+# respective *-available/ counterparts. These should be managed by using our
+# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See
+# their respective man pages for detailed information.
+#
+# * The binary is called apache2. Due to the use of environment variables, in
+# the default configuration, apache2 needs to be started/stopped with
+# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not
+# work with the default configuration.
+
+
+# Global configuration
+#
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# NOTE! If you intend to place this on an NFS (or otherwise network)
+# mounted filesystem then please read the Mutex documentation (available
+# at );
+# you will save yourself a lot of trouble.
+#
+# Do NOT add a slash at the end of the directory path.
+#
+#ServerRoot "/etc/apache2"
+
+#
+# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
+#
+Mutex file:${APACHE_LOCK_DIR} default
+
+#
+# PidFile: The file in which the server should record its process
+# identification number when it starts.
+# This needs to be set in /etc/apache2/envvars
+#
+PidFile ${APACHE_PID_FILE}
+
+#
+# Timeout: The number of seconds before receives and sends time out.
+#
+Timeout 3600
+
+#
+# KeepAlive: Whether or not to allow persistent connections (more than
+# one request per connection). Set to "Off" to deactivate.
+#
+KeepAlive On
+
+#
+# MaxKeepAliveRequests: The maximum number of requests to allow
+# during a persistent connection. Set to 0 to allow an unlimited amount.
+# We recommend you leave this number high, for maximum performance.
+#
+MaxKeepAliveRequests 100
+
+#
+# KeepAliveTimeout: Number of seconds to wait for the next request from the
+# same client on the same connection.
+#
+KeepAliveTimeout 10
+
+
+# These need to be set in /etc/apache2/envvars
+User ${APACHE_RUN_USER}
+Group ${APACHE_RUN_GROUP}
+#
+# DefaultType is the default MIME type the server will use for a document
+# if it cannot otherwise determine one, such as from filename extensions.
+# If your server contains mostly text or HTML documents, "text/plain" is
+# a good value. If most of your content is binary, such as applications
+# or images, you may want to use "application/octet-stream" instead to
+# keep browsers from trying to display binary files as though they are
+# text.
+#
+# It is also possible to omit any default MIME type and let the
+# client's browser guess an appropriate action instead. Typically the
+# browser will decide based on the file's extension then. In cases
+# where no good assumption can be made, letting the default MIME type
+# unset is suggested instead of forcing the browser to accept
+# incorrect metadata.
+#
+DefaultType None
+
+#
+# HostnameLookups: Log the names of clients or just their IP addresses
+# e.g., www.apache.org (on) or 204.62.129.132 (off).
+# The default is off because it'd be overall better for the net if people
+# had to knowingly turn this feature on, since enabling it means that
+# each client request will result in AT LEAST one lookup request to the
+# nameserver.
+#
+HostnameLookups Off
+
+# JHM, 05.19.2015
+# Enable Extended status to allow NewRelic Apache plugin to work
+ExtendedStatus On
+
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a
+# container, that host's errors will be logged there and not here.
+#
+ErrorLog ${APACHE_LOG_DIR}/error.log
+
+#
+# LogLevel: Control the severity of messages logged to the error_log.
+# Available values: trace8, ..., trace1, debug, info, notice, warn,
+# error, crit, alert, emerg.
+# It is also possible to configure the log level for particular modules, e.g.
+# "LogLevel info ssl:warn"
+#
+LogLevel warn
+
+# Include module configuration:
+IncludeOptional mods-enabled/*.load
+IncludeOptional mods-enabled/*.conf
+
+# Include list of ports to listen on
+Include ports.conf
+
+
+ Order allow,deny
+ Deny from all
+ Satisfy all
+
+
+
+
+
+# AccessFileName: The name of the file to look for in each directory
+# for additional configuration directives. See also the AllowOverride
+# directive.
+#
+AccessFileName .htaccess
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+#
+
+ Require all denied
+
+
+# Added for WSGI auth compatibility, JHM 06-19-2015
+#WSGIPythonPath "sys.path+['/usr/local/bin/HostedContentProtector']"
+
+#
+# The following directives define some format nicknames for use with
+# a CustomLog directive.
+#
+# These deviate from the Common Log Format definitions in that they use %O
+# (the actual bytes sent including headers) instead of %b (the size of the
+# requested file), because the latter makes it impossible to detect partial
+# requests.
+#
+# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended.
+# Use mod_remoteip instead.
+#
+LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
+LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
+LogFormat "%h %l %u %t \"%r\" %>s %O" common
+LogFormat "%{Referer}i -> %U" referer
+LogFormat "%{User-agent}i" agent
+
+# Include of directories ignores editors' and dpkg's backup files,
+# see README.Debian for details.
+
+# Include generic snippets of statements
+IncludeOptional conf-enabled/*.conf
+
+# Include the virtual host configurations:
+IncludeOptional sites-enabled/*.conf
+
+# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
\ No newline at end of file
diff --git a/roles/apache/templates/RedHat-httpd.conf.j2 b/roles/apache/templates/RedHat-httpd.conf.j2
new file mode 100644
index 0000000..1ed0f70
--- /dev/null
+++ b/roles/apache/templates/RedHat-httpd.conf.j2
@@ -0,0 +1,354 @@
+#
+# This is the main Apache HTTP server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See for detailed information.
+# In particular, see
+#
+# for a discussion of each configuration directive.
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+# Configuration and logfile names: If the filenames you specify for many
+# of the server's control files begin with "/" (or "drive:/" for Win32), the
+# server will use that explicit path. If the filenames do *not* begin
+# with "/", the value of ServerRoot is prepended -- so 'log/access_log'
+# with ServerRoot set to '/www' will be interpreted by the
+# server as '/www/log/access_log', where as '/log/access_log' will be
+# interpreted as '/log/access_log'.
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# Do not add a slash at the end of the directory path. If you point
+# ServerRoot at a non-local disk, be sure to specify a local disk on the
+# Mutex directive, if file-based mutexes are used. If you wish to share the
+# same ServerRoot for multiple httpd daemons, you will need to change at
+# least PidFile.
+#
+ServerRoot "/etc/httpd"
+
+#
+# Listen: Allows you to bind Apache to specific IP addresses and/or
+# ports, instead of the default. See also the
+# directive.
+#
+# Change this to Listen on specific IP addresses as shown below to
+# prevent Apache from glomming onto all bound IP addresses.
+#
+#Listen 12.34.56.78:80
+Listen 80
+Listen 443 https
+
+#
+# Dynamic Shared Object (DSO) Support
+#
+# To be able to use the functionality of a module which was built as a DSO you
+# have to place corresponding `LoadModule' lines at this location so the
+# directives contained in it are actually available _before_ they are used.
+# Statically compiled modules (those listed by `httpd -l') do not need
+# to be loaded here.
+#
+# Example:
+# LoadModule foo_module modules/mod_foo.so
+#
+Include conf.modules.d/*.conf
+
+#
+# If you wish httpd to run as a different user or group, you must run
+# httpd as root initially and it will switch.
+#
+# User/Group: The name (or #number) of the user/group to run httpd as.
+# It is usually good practice to create a dedicated user and group for
+# running httpd, as with most system services.
+#
+User apache
+Group apache
+
+# 'Main' server configuration
+#
+# The directives in this section set up the values used by the 'main'
+# server, which responds to any requests that aren't handled by a
+# definition. These values also provide defaults for
+# any containers you may define later in the file.
+#
+# All of these directives may appear inside containers,
+# in which case these default settings will be overridden for the
+# virtual host being defined.
+#
+
+#
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed. This address appears on some server-generated pages, such
+# as error documents. e.g. admin@your-domain.com
+#
+ServerAdmin root@localhost
+
+#
+# ServerName gives the name and port that the server uses to identify itself.
+# This can often be determined automatically, but we recommend you specify
+# it explicitly to prevent problems during startup.
+#
+# If your host doesn't have a registered DNS name, enter its IP address here.
+#
+#ServerName www.example.com:80
+
+#
+# Deny access to the entirety of your server's filesystem. You must
+# explicitly permit access to web content directories in other
+# blocks below.
+#
+
+ AllowOverride none
+ Require all denied
+
+
+#
+# Note that from this point forward you must specifically allow
+# particular features to be enabled - so if something's not working as
+# you might expect, make sure that you have specifically enabled it
+# below.
+#
+
+#
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+#
+DocumentRoot "/var/www/html"
+
+#
+# Relax access to content within /var/www.
+#
+
+ AllowOverride None
+ # Allow open access:
+ Require all granted
+
+
+# Further relax access to the default document root:
+
+ #
+ # Possible values for the Options directive are "None", "All",
+ # or any combination of:
+ # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
+ #
+ # Note that "MultiViews" must be named *explicitly* --- "Options All"
+ # doesn't give it to you.
+ #
+ # The Options directive is both complicated and important. Please see
+ # http://httpd.apache.org/docs/2.4/mod/core.html#options
+ # for more information.
+ #
+ Options Indexes FollowSymLinks
+
+ #
+ # AllowOverride controls what directives may be placed in .htaccess files.
+ # It can be "All", "None", or any combination of the keywords:
+ # Options FileInfo AuthConfig Limit
+ #
+ AllowOverride None
+
+ #
+ # Controls who can get stuff from this server.
+ #
+ Require all granted
+
+
+#
+# DirectoryIndex: sets the file that Apache will serve if a directory
+# is requested.
+#
+
+ DirectoryIndex index.html
+
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+#
+
+ Require all denied
+
+
+#
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a
+# container, that host's errors will be logged there and not here.
+#
+ErrorLog "logs/error_log"
+
+#
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+#
+LogLevel warn
+
+
+ #
+ # The following directives define some format nicknames for use with
+ # a CustomLog directive (see below).
+ #
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+ LogFormat "%h %l %u %t \"%r\" %>s %b" common
+
+
+ # You need to enable mod_logio.c to use %I and %O
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
+
+
+ #
+ # The location and format of the access logfile (Common Logfile Format).
+ # If you do not define any access logfiles within a
+ # container, they will be logged here. Contrariwise, if you *do*
+ # define per- access logfiles, transactions will be
+ # logged therein and *not* in this file.
+ #
+ #CustomLog "logs/access_log" common
+
+ #
+ # If you prefer a logfile with access, agent, and referer information
+ # (Combined Logfile Format) you can use the following directive.
+ #
+ CustomLog "logs/access_log" combined
+
+
+
+ #
+ # Redirect: Allows you to tell clients about documents that used to
+ # exist in your server's namespace, but do not anymore. The client
+ # will make a new request for the document at its new location.
+ # Example:
+ # Redirect permanent /foo http://www.example.com/bar
+
+ #
+ # Alias: Maps web paths into filesystem paths and is used to
+ # access content that does not live under the DocumentRoot.
+ # Example:
+ # Alias /webpath /full/filesystem/path
+ #
+ # If you include a trailing / on /webpath then the server will
+ # require it to be present in the URL. You will also likely
+ # need to provide a section to allow access to
+ # the filesystem path.
+
+ #
+ # ScriptAlias: This controls which directories contain server scripts.
+ # ScriptAliases are essentially the same as Aliases, except that
+ # documents in the target directory are treated as applications and
+ # run by the server when requested rather than as documents sent to the
+ # client. The same rules about trailing "/" apply to ScriptAlias
+ # directives as to Alias.
+ #
+ ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
+
+
+
+#
+# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased
+# CGI directory exists, if you have that configured.
+#
+
+ AllowOverride None
+ Options None
+ Require all granted
+
+
+
+ #
+ # TypesConfig points to the file containing the list of mappings from
+ # filename extension to MIME-type.
+ #
+ TypesConfig /etc/mime.types
+
+ #
+ # AddType allows you to add to or override the MIME configuration
+ # file specified in TypesConfig for specific file types.
+ #
+ #AddType application/x-gzip .tgz
+ #
+ # AddEncoding allows you to have certain browsers uncompress
+ # information on the fly. Note: Not all browsers support this.
+ #
+ #AddEncoding x-compress .Z
+ #AddEncoding x-gzip .gz .tgz
+ #
+ # If the AddEncoding directives above are commented-out, then you
+ # probably should define those extensions to indicate media types:
+ #
+ AddType application/x-compress .Z
+ AddType application/x-gzip .gz .tgz
+
+ #
+ # AddHandler allows you to map certain file extensions to "handlers":
+ # actions unrelated to filetype. These can be either built into the server
+ # or added with the Action directive (see below)
+ #
+ # To use CGI scripts outside of ScriptAliased directories:
+ # (You will also need to add "ExecCGI" to the "Options" directive.)
+ #
+ #AddHandler cgi-script .cgi
+
+ # For type maps (negotiated resources):
+ #AddHandler type-map var
+
+ #
+ # Filters allow you to process content before it is sent to the client.
+ #
+ # To parse .shtml files for server-side includes (SSI):
+ # (You will also need to add "Includes" to the "Options" directive.)
+ #
+ AddType text/html .shtml
+ AddOutputFilter INCLUDES .shtml
+
+
+#
+# Specify a default charset for all content served; this enables
+# interpretation of all content as UTF-8 by default. To use the
+# default browser choice (ISO-8859-1), or to allow the META tags
+# in HTML content to override this choice, comment out this
+# directive:
+#
+AddDefaultCharset UTF-8
+
+
+ #
+ # The mod_mime_magic module allows the server to use various hints from the
+ # contents of the file itself to determine its type. The MIMEMagicFile
+ # directive tells the module where the hint definitions are located.
+ #
+ MIMEMagicFile conf/magic
+
+
+#
+# Customizable error responses come in three flavors:
+# 1) plain text 2) local redirects 3) external redirects
+#
+# Some examples:
+#ErrorDocument 500 "The server made a boo boo."
+#ErrorDocument 404 /missing.html
+#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
+#ErrorDocument 402 http://www.example.com/subscription_info.html
+#
+
+#
+# EnableMMAP and EnableSendfile: On systems that support it,
+# memory-mapping or the sendfile syscall may be used to deliver
+# files. This usually improves server performance, but must
+# be turned off when serving from networked-mounted
+# filesystems or if support for these functions is otherwise
+# broken on your system.
+# Defaults if commented: EnableMMAP On, EnableSendfile Off
+#
+#EnableMMAP off
+EnableSendfile on
+
+# Supplemental configuration
+#
+# Load config files in the "/etc/httpd/conf.d" directory, if any.
+IncludeOptional conf.d/*.conf
diff --git a/roles/apache/templates/apache2.logrotate b/roles/apache/templates/apache2.logrotate
new file mode 100644
index 0000000..31f8c38
--- /dev/null
+++ b/roles/apache/templates/apache2.logrotate
@@ -0,0 +1,15 @@
+/var/log/apache2/*.log {
+ daily
+ missingok
+ rotate 365
+ compress
+ delaycompress
+ notifempty
+ create 640 root adm
+ sharedscripts
+ postrotate
+ if [ -f "`. /etc/apache2/envvars ; echo ${APACHE_PID_FILE:-/var/run/apache2.pid}`" ]; then
+ /etc/init.d/apache2 reload > /dev/null
+ fi
+ endscript
+}
\ No newline at end of file
diff --git a/roles/apache/templates/install_mod_jk.sh b/roles/apache/templates/install_mod_jk.sh
new file mode 100644
index 0000000..8253aaa
--- /dev/null
+++ b/roles/apache/templates/install_mod_jk.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+cd /opt/mod_jk/tomcat-connectors-1.2.46-src/native
+
+./configure --with-apxs=/usr/bin/apxs
+make
+libtool --finish /usr/lib64/httpd/modules
+make install
diff --git a/roles/apache/templates/jk.conf.j2 b/roles/apache/templates/jk.conf.j2
new file mode 100644
index 0000000..1cf66b3
--- /dev/null
+++ b/roles/apache/templates/jk.conf.j2
@@ -0,0 +1,13 @@
+{% if ansible_os_family == 'RedHat' %}
+LoadModule jk_module "{{ apache_server_root }}/modules/mod_jk.so"
+JkShmFile /var/run/httpd/mod_jk.shm
+{% endif %}
+
+JkWorkersFile /etc/libapache2-mod-jk/workers.properties
+JkLogFile {{ apache_logs }}/mod_jk.log
+JkLogLevel info
+JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
+JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories
+JkRequestLogFormat "%w %V %T"
+
+JkAutoAlias {{ tomcat_base }}
diff --git a/roles/apache/templates/max_clients.conf.j2 b/roles/apache/templates/max_clients.conf.j2
new file mode 100644
index 0000000..848ac12
--- /dev/null
+++ b/roles/apache/templates/max_clients.conf.j2
@@ -0,0 +1,12 @@
+# prefork MPM
+# StartServers: number of server processes to start
+# MinSpareServers: minimum number of server processes which are kept spare
+# MaxSpareServers: maximum number of server processes which are kept spare
+# MaxClients: maximum number of server processes allowed to start
+# MaxRequestsPerChild: maximum number of requests a server process serves
+
+ MaxClients 250
+
+ # since we're keeping a client per connection, kill off unused connections after 2 seconds (instead of 5)
+ KeepAliveTimeout 2
+
diff --git a/roles/apache/templates/mpm_prefork.conf.j2 b/roles/apache/templates/mpm_prefork.conf.j2
new file mode 100644
index 0000000..8a7fccd
--- /dev/null
+++ b/roles/apache/templates/mpm_prefork.conf.j2
@@ -0,0 +1,52 @@
+# prefork MPM
+# StartServers: number of server processes to start
+# MinSpareServers: minimum number of server processes which are kept spare
+# MaxSpareServers: maximum number of server processes which are kept spare
+# MaxRequestWorkers: maximum number of server processes allowed to start
+# MaxConnectionsPerChild: maximum number of requests a server process serves
+
+# We start a lot of servers here. Since Apache isn't doing much beyond proxying
+# things, we can start a ton of processes with very little overhead and ensure that lots of servers
+# are available to pick up slack during spikes.
+
+# Each process uses between 5 and 6 MB of physical memory
+# So when it's blown up at 300 Workers you're using about 1800MB
+# That's as much as the application processes on the server can likely handle on typical boxen
+
+# The Application servers aren't really RAM limited - they're more CPU and database i/o bound.
+# So giving Apache lots of RAM to work with is a good trade.
+
+# The MaxRequestWorkers param is controlled by Ansible, so you'll want to set it explicitly in your configs
+# for a production environment.
+
+{% if use_ssl %}
+
+# These settings are appropriate for a box with 4GB RAM that is doing SSL termination.
+# If you're doing SSL termination in Apache, these numbers will have an outsized effect on CPU
+# Start with the default of 150 and tweak from there.
+
+
+ StartServers 30
+ MinSpareServers 15
+ MaxSpareServers 30
+ MaxRequestWorkers {{ MaxRequestWorkers }}
+ MaxConnectionsPerChild 10000
+
+
+{% else %}
+
+# These settings are appropriate for a box with 4GB RAM that isn't doing SSL termination.
+# You can take the MaxRequestWorkers param quite high on a non-ssl box: In production on c3.large
+# instances we routinely set this to 500
+
+
+ StartServers 75
+ MinSpareServers 30
+ MaxSpareServers 75
+ MaxRequestWorkers {{ MaxRequestWorkers }}
+ MaxConnectionsPerChild 10000
+
+
+{% endif %}
+
+# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
diff --git a/roles/apache/templates/robots.txt b/roles/apache/templates/robots.txt
new file mode 100644
index 0000000..1f53798
--- /dev/null
+++ b/roles/apache/templates/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/roles/apache/templates/security.conf.j2 b/roles/apache/templates/security.conf.j2
new file mode 100644
index 0000000..8b9b7c1
--- /dev/null
+++ b/roles/apache/templates/security.conf.j2
@@ -0,0 +1,78 @@
+#
+# Disable access to the entire file system except for the directories that
+# are explicitly allowed later.
+#
+# This currently breaks the configurations that come with some web application
+# Debian packages.
+#
+#
+# AllowOverride None
+# Order Deny,Allow
+# Deny from all
+#
+
+
+# Changing the following options will not really affect the security of the
+# server, but might make attacks slightly more difficult in some cases.
+
+#
+# ServerTokens
+# This directive configures what you return as the Server HTTP response
+# Header. The default is 'Full' which sends information about the OS-Type
+# and compiled in modules.
+# Set to one of: Full | OS | Minimal | Minor | Major | Prod
+# where Full conveys the most information, and Prod the least.
+#
+#ServerTokens Minimal
+#ServerTokens OS
+#ServerTokens Full
+ServerTokens Prod
+
+#
+# Optionally add a line containing the server version and virtual host
+# name to server-generated pages (internal error documents, FTP directory
+# listings, mod_status and mod_info output etc., but not CGI generated
+# documents or custom error documents).
+# Set to "EMail" to also include a mailto: link to the ServerAdmin.
+# Set to one of: On | Off | EMail
+#
+ServerSignature Off
+#ServerSignature On
+
+#
+# Allow TRACE method
+#
+# Set to "extended" to also reflect the request body (only for testing and
+# diagnostic purposes).
+#
+# Set to one of: On | Off | extended
+#
+TraceEnable Off
+#TraceEnable On
+
+#
+# Forbid access to version control directories
+#
+# If you use version control systems in your document root, you should
+# probably deny access to their directories. For example, for subversion:
+#
+#
+# Require all denied
+#
+
+#
+# Setting this header will prevent MSIE from interpreting files as something
+# else than declared by the content type in the HTTP headers.
+# Requires mod_headers to be enabled.
+#
+#Header set X-Content-Type-Options: "nosniff"
+
+#
+# Setting this header will prevent other sites from embedding pages from this
+# site as frames. This defends against clickjacking attacks.
+# Requires mod_headers to be enabled.
+#
+#Header set X-Frame-Options: "sameorigin"
+
+
+# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
diff --git a/roles/apache/templates/ssl.conf.j2 b/roles/apache/templates/ssl.conf.j2
new file mode 100644
index 0000000..22f3057
--- /dev/null
+++ b/roles/apache/templates/ssl.conf.j2
@@ -0,0 +1,91 @@
+
+
+ # Pseudo Random Number Generator (PRNG):
+ # Configure one or more sources to seed the PRNG of the SSL library.
+ # The seed data should be of good random quality.
+ # WARNING! On some platforms /dev/random blocks if not enough entropy
+ # is available. This means you then cannot use the /dev/random device
+ # because it would lead to very long connection times (as long as
+ # it requires to make more entropy available). But usually those
+ # platforms additionally provide a /dev/urandom device which doesn't
+ # block. So, if available, use this one instead. Read the mod_ssl User
+ # Manual for more details.
+ #
+ SSLRandomSeed startup builtin
+ SSLRandomSeed startup file:/dev/urandom 512
+ SSLRandomSeed connect builtin
+ SSLRandomSeed connect file:/dev/urandom 512
+
+ ##
+ ## SSL Global Context
+ ##
+ ## All SSL configuration in this context applies both to
+ ## the main server and all SSL-enabled virtual hosts.
+ ##
+
+ #
+ # Some MIME-types for downloading Certificates and CRLs
+ #
+ AddType application/x-x509-ca-cert .crt
+ AddType application/x-pkcs7-crl .crl
+
+ # Pass Phrase Dialog:
+ # Configure the pass phrase gathering process.
+ # The filtering dialog program (`builtin' is a internal
+ # terminal dialog) has to provide the pass phrase on stdout.
+ SSLPassPhraseDialog {{ apache_ssl_pass_phrase_dialog }}
+
+ # Inter-Process Session Cache:
+ # Configure the SSL Session Cache: First the mechanism
+ # to use and second the expiring timeout (in seconds).
+ # (The mechanism dbm has known memory leaks and should not be used).
+ #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache
+ SSLSessionCache {{ apache_ssl_session_cache }}
+ SSLSessionCacheTimeout 3600
+
+ # Semaphore:
+ # Configure the path to the mutual exclusion semaphore the
+ # SSL engine uses internally for inter-process synchronization.
+ # (Disabled by default, the global Mutex directive consolidates by default
+ # this)
+ #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache
+
+ # These settings are based on https://mozilla.github.io/server-side-tls/ssl-config-generator/
+ # for Apache with Intermediate browser support
+
+ # SSL Cipher Suite:
+ # List the ciphers that the client is permitted to negotiate. See the
+ # ciphers(1) man page from the openssl package for list of all available
+ # options.
+ #
+ # Speed-optimized SSL Cipher configuration:
+ # If speed is your main concern (on busy HTTPS servers e.g.),
+ # you might want to force clients to specific, performance
+ # optimized ciphers. In this case, prepend those ciphers
+ # to the SSLCipherSuite list, and enable SSLHonorCipherOrder.
+ SSLCipherSuite {{ssl_cipher_suite}}
+ SSLHonorCipherOrder on
+ SSLCompression off
+
+ # OCSP Stapling, only in httpd 2.3.3 and later
+ SSLUseStapling on
+ SSLStaplingResponderTimeout 5
+ SSLStaplingReturnResponderErrors off
+ SSLStaplingCache shmcb:/var/run/ocsp(128000)
+
+ # The protocols to enable.
+ # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2
+ # SSL v2 is no longer supported
+ SSLProtocol {{ssl_protocol}}
+
+ # Allow insecure renegotiation with clients which do not yet support the
+ # secure renegotiation protocol. Default: Off
+ #SSLInsecureRenegotiation on
+
+ # Whether to forbid non-SNI clients to access name based virtual hosts.
+ # Default: Off
+ #SSLStrictSNIVHostCheck On
+
+
+
+# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
diff --git a/roles/apache/templates/workers.properties.j2 b/roles/apache/templates/workers.properties.j2
new file mode 100644
index 0000000..aa5c2fe
--- /dev/null
+++ b/roles/apache/templates/workers.properties.j2
@@ -0,0 +1,16 @@
+workers.tomcat_home={{ tomcat_base }}
+workers.java_home={{ java_home }}
+ps=/
+
+worker.list=ajp13_worker
+
+worker.ajp13_worker.port=8080
+worker.ajp13_worker.host=127.0.0.1
+worker.ajp13_worker.type=ajp13
+worker.ajp13_worker.lbfactor=1
+worker.ajp13_worker.connect_timeout=10000
+worker.ajp13_worker.prepost_timeout=10000
+worker.ajp13_worker.socket_timeout=300
+worker.ajp13_worker.connection_pool_timeout=30
+worker.ajp13_worker.max_packet_size=16384
+worker.ajp13_worker.secret=ZpVVboYmRQuWaTeKBFXYEKdipqBTJxJHKyhXTi8QwCkepvNQXKdDtJTTYoGLGiUq
diff --git a/roles/apache/vars/Debian.yml b/roles/apache/vars/Debian.yml
new file mode 100644
index 0000000..e4ef44c
--- /dev/null
+++ b/roles/apache/vars/Debian.yml
@@ -0,0 +1,18 @@
+---
+apache_service: apache2
+apache_daemon: apache2
+apache_daemon_path: /usr/sbin/
+apache_server_root: /etc/apache2
+apache_conf_path: "{{ apache_server_root}}/conf-available"
+apache_mods_path: "{{ apache_server_root}}/mods-available"
+apache_sites_path: "{{ apache_server_root}}/sites-available"
+
+apache_logs: "/var/log/apache2"
+
+__apache_packages:
+ - apache2
+ - apache2-utils
+ - libapache2-mod-jk
+
+apache_ssl_pass_phrase_dialog: "exec:/usr/share/apache2/ask-for-passphrase"
+apache_ssl_session_cache: "shmcb:${APACHE_RUN_DIR}/ssl_scache(512000)"
diff --git a/roles/apache/vars/RedHat.yml b/roles/apache/vars/RedHat.yml
new file mode 100644
index 0000000..1bc2904
--- /dev/null
+++ b/roles/apache/vars/RedHat.yml
@@ -0,0 +1,19 @@
+---
+apache_service: httpd
+apache_daemon: httpd
+apache_daemon_path: /usr/sbin/
+apache_server_root: /etc/httpd
+apache_conf_path: "{{ apache_server_root}}/conf.d"
+apache_mods_path: "{{ apache_server_root}}/conf.modules.d"
+apache_sites_path: "{{ apache_server_root}}/conf.d"
+
+apache_logs: "/var/log/httpd"
+
+__apache_packages:
+ - httpd
+ - httpd-devel
+ - mod_ssl
+ - gcc # Used to configure/build zlib, if zlib is not currently installed
+
+apache_ssl_pass_phrase_dialog: "exec:/usr/libexec/httpd-ssl-pass-dialog"
+apache_ssl_session_cache: "shmcb:/run/httpd/sslcache(512000)"
diff --git a/roles/aws-s3/defaults/main.yml b/roles/aws-s3/defaults/main.yml
new file mode 100644
index 0000000..d549b4e
--- /dev/null
+++ b/roles/aws-s3/defaults/main.yml
@@ -0,0 +1,17 @@
+---
+
+# Defaults
+
+env: dev
+# If set to true, Slartibartfast will delete your S3 bucket when you run this play.
+# As such, DONT SET IT TO TRUE.
+Slartibartfast: false
+
+S3FileStorageRegion: "us-east-1"
+
+S3Endpoint: "s3-external-1.amazonaws.com"
+
+S3FileStorageTempDir: "{{ data_root }}/s3temp"
+
+terraform: false
+
diff --git a/roles/aws-s3/files/README.md b/roles/aws-s3/files/README.md
new file mode 100644
index 0000000..b566a52
--- /dev/null
+++ b/roles/aws-s3/files/README.md
@@ -0,0 +1,3 @@
+# IAM and S3 bucket policy docs will be rendered in this folder.
+
+This file is here as a placeholder, please don't remove it.
diff --git a/roles/aws-s3/tasks/main.yml b/roles/aws-s3/tasks/main.yml
new file mode 100644
index 0000000..a3f55c8
--- /dev/null
+++ b/roles/aws-s3/tasks/main.yml
@@ -0,0 +1,224 @@
+---
+
+# Note that this playbook REQUIRES that you use your own AWS Environment vars/boto config
+# It's not going to use any of the vars set in the playbooks or ansible environment vars
+
+ - name: create AWS S3 Bucket
+ s3_bucket:
+ name: "{{ S3FileStoragePrefix }}-cc-content"
+ state: present
+ versioning: yes
+ tags:
+ domain: "{{ ServerName }}"
+ application: "ContentController"
+ environment: "{{ env }}"
+ register: bucket
+ when: not Slartibartfast
+
+ - name: create AWS S3 log Bucket
+ s3_bucket:
+ name: "{{ S3FileStoragePrefix }}-cc-content-logs"
+ state: present
+ versioning: yes
+ tags:
+ domain: "{{ ServerName }}"
+ application: "ContentController"
+ environment: "{{ env }}"
+ register: logbucket
+ when: not Slartibartfast
+
+ - debug: var=bucket
+
+ - name: Create IAM group
+ iam:
+ iam_type: group
+ name: "{{ item }}"
+ state: present
+ with_items:
+ - "{{ ClientName }}ContentControllerS3Users"
+ register: iamgroups
+ when: not Slartibartfast
+
+ - debug: var=iamgroups
+
+ - name: Create a new IAM user with API keys
+ iam:
+ iam_type: user
+ name: "{{ item }}"
+ state: present
+ groups: "{{ ClientName }}ContentControllerS3Users"
+ access_key_state: create
+ with_items:
+ - "{{ S3FileStorageIAMUsernamePrompt }}"
+ register: user
+ when: not Slartibartfast
+
+ - debug: var=user
+
+ - set_fact:
+ S3FileStorageAwsId: "{{ user.results[0].user_meta.access_keys[0].access_key_id }}"
+ when: user.results[0].changed
+
+ - set_fact:
+ S3FileStorageAwsKey: "{{ user.results[0].user_meta.access_keys[0].secret_access_key }}"
+ when: user.results[0].changed
+
+ - set_fact:
+ S3FileStorageBucket: "{{ bucket.name }}"
+ when: bucket.changed
+
+ - set_fact:
+ S3LogStorageBucket: "{{ logbucket.name }}"
+ when: logbucket.changed
+
+ - name: Write the results to the S3 vars file
+ local_action: template src="s3.j2" dest="group_vars/s3.yml"
+ when: (user.results[0].changed or bucket.changed) and not Slartibartfast and not terraform
+
+ - name: Write the CloudFront Origin Access Identity to the CloudFront vars file
+ local_action:
+ module: lineinfile
+ args:
+ dest: group_vars/cloudfront.yml
+ regexp: "^cloudfront_origin_access_identity:"
+ line: "cloudfront_origin_access_identity: {{ cloudfront_origin_access_identity }}"
+ when: (user.results[0].changed or bucket.changed) and not Slartibartfast
+
+ - name: Enable S3 file storage
+ local_action:
+ module: lineinfile
+ args:
+ dest: group_vars/env.yml
+ regexp: "^S3FileStorageEnabled:"
+ line: "S3FileStorageEnabled: true"
+ when: (user.results[0].changed or bucket.changed) and not Slartibartfast
+
+ - name: render AWS-IAM Template
+ local_action: template src="AWS-IAM-Policy.json.j2" dest="roles/aws-s3/files/AWS-IAM-Policy.json"
+ when: (user.results[0].changed or bucket.changed) and not Slartibartfast
+
+ - name: render AWS-IAM Log Template
+ local_action: template src="AWS-IAM-LogPolicy.json.j2" dest="roles/aws-s3/files/AWS-IAM-LogPolicy.json"
+ when: (user.results[0].changed or logbucket.changed) and not Slartibartfast
+
+ - name: render AWS s3 Bucket Policy Template
+ local_action: template src="AWS-S3-BucketPolicy.json.j2" dest="roles/aws-s3/files/AWS-S3-BucketPolicy.json"
+ when: (user.results[0].changed or bucket.changed) and not Slartibartfast
+
+ - name: render AWS s3 Log Bucket Policy Template
+ local_action: template src="AWS-S3-LogBucketPolicy.json.j2" dest="roles/aws-s3/files/AWS-S3-LogBucketPolicy.json"
+ when: (user.results[0].changed or logbucket.changed) and not Slartibartfast
+
+ - name: Sleep for 30 seconds, which allows the bucket policy to finish writing to disk and for all the bits to become consistent.
+ pause: seconds=30
+ when: not Slartibartfast
+
+ - name: apply S3 bucket policy to the bucket
+ s3_bucket:
+ name: "{{ bucket.name }}"
+ policy: "{{ lookup('file', 'roles/aws-s3/files/AWS-S3-BucketPolicy.json') }}"
+ when: (user.results[0].changed or bucket.changed) and not Slartibartfast
+
+ - name: apply S3 bucket policy to the log bucket
+ s3_bucket:
+ name: "{{ logbucket.name }}"
+ policy: "{{ lookup('file', 'roles/aws-s3/files/AWS-S3-LogBucketPolicy.json') }}"
+ when: (user.results[0].changed or logbucket.changed) and not Slartibartfast
+
+ - name: Apply IAM policy to the new groups
+ iam_policy:
+ iam_type: group
+ iam_name: "{{ item.group_name }}"
+ policy_name: "{{ ClientName }}-ContentController-S3-ReadWrite"
+ policy_document: "roles/aws-s3/files/AWS-IAM-Policy.json"
+ state: present
+ with_items: "{{ iamgroups.results }}"
+ when: (user.results[0].changed or bucket.changed) and not Slartibartfast
+
+ - name: Apply LogBucket IAM policy to the new groups
+ iam_policy:
+ iam_type: group
+ iam_name: "{{ item.group_name }}"
+ policy_name: "{{ ClientName }}-ContentController-S3-LogReadWrite"
+ policy_document: "roles/aws-s3/files/AWS-IAM-LogPolicy.json"
+ state: present
+ with_items: "{{ iamgroups.results }}"
+ when: (user.results[0].changed or logbucket.changed) and not Slartibartfast
+
+ - name: Write the results to the S3 vars file
+ local_action: template src="s3.j2" dest="group_vars/s3.yml"
+ when: (user.results[0].changed or bucket.changed) and not Slartibartfast and not terraform
+
+ - name: Set S3 AccountID in consul
+ consul_kv:
+ key: "{{ ServerName }}/S3AWSAccountID"
+ state: present
+ value: "{{ S3AWSAccountIDPrompt }}"
+ when: not Slartibartfast and terraform and (consul is defined and consul|bool == true)
+
+ - name: Set S3FileStorageIAMUsername in consul
+ consul_kv:
+ key: "{{ ServerName }}/S3FileStorageIAMUsername"
+ state: present
+ value: "{{ S3FileStorageIAMUsernamePrompt }}"
+ when: not Slartibartfast and terraform and (consul is defined and consul|bool == true)
+
+ - name: Set S3FileStorageAwsId in consul
+ consul_kv:
+ key: "{{ ServerName }}/S3FileStorageAwsId"
+ state: present
+ value: "{{ S3FileStorageAwsId }}"
+ when: not Slartibartfast and terraform and (consul is defined and consul|bool == true)
+
+ - name: Set S3FileStorageAwsKey in consul
+ consul_kv:
+ key: "{{ ServerName }}/S3FileStorageAwsKey"
+ state: present
+ value: "{{ S3FileStorageAwsKey }}"
+ when: not Slartibartfast and terraform and (consul is defined and consul|bool == true)
+
+ - name: Update S3 bucket definition in Consul for Terraforms use
+ consul_kv:
+ key: "{{ ServerName }}/s3_content_bucket"
+ state: present
+ value: "{{ bucket.name }}"
+ when: not Slartibartfast and terraform and (consul is defined and consul|bool == true)
+
+ - name: Update S3 log bucket definition in Consul for Terraforms use
+ consul_kv:
+ key: "{{ ServerName }}/s3_log_bucket"
+ state: present
+ value: "{{ logbucket.name }}"
+ when: not Slartibartfast and terraform and (consul is defined and consul|bool == true)
+
+ - name: Set S3FileStorageRegion in consul
+ consul_kv:
+ key: "{{ ServerName }}/S3FileStorageRegion"
+ state: present
+ value: "{{ S3FileStorageRegion }}"
+ when: not Slartibartfast and terraform and (consul is defined and consul|bool == true)
+
+ - name: Delete the bucket if desired
+ s3_bucket:
+ name: "{{ S3FileStoragePrefix }}-cc-content"
+ state: absent
+ force: yes
+ when: Slartibartfast
+
+ - name: Delete the log bucket if desired
+ s3_bucket:
+ name: "{{ S3FileStoragePrefix }}-cc-content-logs"
+ state: absent
+ force: yes
+ when: Slartibartfast
+
+ - name: Delete the user if desired
+ iam:
+ iam_type: user
+ name: "{{ S3FileStorageIAMUsernamePrompt }}"
+ state: absent
+ when: Slartibartfast
+
+ - name: Let the user know where to find things
+ debug: msg="We have created your s3 config file and all of the s3 resources Content Controller needs in order to use it. Look at group_vars/s3.yml for all of your settings."
+ when: not Slartibartfast and not terraform
diff --git a/roles/aws-s3/templates/AWS-IAM-LogPolicy.json.j2 b/roles/aws-s3/templates/AWS-IAM-LogPolicy.json.j2
new file mode 100644
index 0000000..1618ad3
--- /dev/null
+++ b/roles/aws-s3/templates/AWS-IAM-LogPolicy.json.j2
@@ -0,0 +1,74 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "AllowGroupToSeeBucketListAndAlsoAllowGetBucketLocationRequiredForListBucket",
+ "Action": [
+ "s3:ListAllMyBuckets",
+ "s3:GetBucketLocation"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:s3:::{{ logbucket.name }}"
+ ]
+ },
+ {
+ "Sid": "AllowListBucket",
+ "Action": [
+ "s3:ListBucket"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:s3:::{{ logbucket.name }}/*"
+ ]
+ },
+ {
+ "Sid": "Stmt1446047952000",
+ "Effect": "Allow",
+ "Action": [
+ "s3:AbortMultipartUpload",
+ "s3:CreateBucket",
+ "s3:DeleteObject",
+ "s3:DeleteObjectVersion",
+ "s3:GetBucketAcl",
+ "s3:GetBucketCORS",
+ "s3:GetBucketLocation",
+ "s3:GetBucketLogging",
+ "s3:GetBucketNotification",
+ "s3:GetBucketPolicy",
+ "s3:GetBucketRequestPayment",
+ "s3:GetBucketTagging",
+ "s3:GetBucketVersioning",
+ "s3:GetBucketWebsite",
+ "s3:GetLifecycleConfiguration",
+ "s3:GetObject",
+ "s3:GetObjectAcl",
+ "s3:GetObjectTorrent",
+ "s3:GetObjectVersion",
+ "s3:GetObjectVersionAcl",
+ "s3:GetObjectVersionTorrent",
+ "s3:ListBucket",
+ "s3:ListAllMyBuckets",
+ "s3:ListBucketMultipartUploads",
+ "s3:ListBucketVersions",
+ "s3:ListMultipartUploadParts",
+ "s3:PutBucketAcl",
+ "s3:PutBucketCORS",
+ "s3:PutBucketLogging",
+ "s3:PutBucketNotification",
+ "s3:PutBucketRequestPayment",
+ "s3:PutBucketTagging",
+ "s3:PutBucketVersioning",
+ "s3:PutBucketWebsite",
+ "s3:PutLifecycleConfiguration",
+ "s3:PutObject",
+ "s3:PutObjectAcl",
+ "s3:PutObjectVersionAcl",
+ "s3:RestoreObject"
+ ],
+ "Resource": [
+ "arn:aws:s3:::{{ logbucket.name }}/*"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/roles/aws-s3/templates/AWS-IAM-Policy.json.j2 b/roles/aws-s3/templates/AWS-IAM-Policy.json.j2
new file mode 100644
index 0000000..11656de
--- /dev/null
+++ b/roles/aws-s3/templates/AWS-IAM-Policy.json.j2
@@ -0,0 +1,74 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "AllowGroupToSeeBucketListAndAlsoAllowGetBucketLocationRequiredForListBucket",
+ "Action": [
+ "s3:ListAllMyBuckets",
+ "s3:GetBucketLocation"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:s3:::*"
+ ]
+ },
+ {
+ "Sid": "AllowListBucket",
+ "Action": [
+ "s3:ListBucket"
+ ],
+ "Effect": "Allow",
+ "Resource": [
+ "arn:aws:s3:::{{ bucket.name }}/*"
+ ]
+ },
+ {
+ "Sid": "Stmt1446047952000",
+ "Effect": "Allow",
+ "Action": [
+ "s3:AbortMultipartUpload",
+ "s3:CreateBucket",
+ "s3:DeleteObject",
+ "s3:DeleteObjectVersion",
+ "s3:GetBucketAcl",
+ "s3:GetBucketCORS",
+ "s3:GetBucketLocation",
+ "s3:GetBucketLogging",
+ "s3:GetBucketNotification",
+ "s3:GetBucketPolicy",
+ "s3:GetBucketRequestPayment",
+ "s3:GetBucketTagging",
+ "s3:GetBucketVersioning",
+ "s3:GetBucketWebsite",
+ "s3:GetLifecycleConfiguration",
+ "s3:GetObject",
+ "s3:GetObjectAcl",
+ "s3:GetObjectTorrent",
+ "s3:GetObjectVersion",
+ "s3:GetObjectVersionAcl",
+ "s3:GetObjectVersionTorrent",
+ "s3:ListBucket",
+ "s3:ListAllMyBuckets",
+ "s3:ListBucketMultipartUploads",
+ "s3:ListBucketVersions",
+ "s3:ListMultipartUploadParts",
+ "s3:PutBucketAcl",
+ "s3:PutBucketCORS",
+ "s3:PutBucketLogging",
+ "s3:PutBucketNotification",
+ "s3:PutBucketRequestPayment",
+ "s3:PutBucketTagging",
+ "s3:PutBucketVersioning",
+ "s3:PutBucketWebsite",
+ "s3:PutLifecycleConfiguration",
+ "s3:PutObject",
+ "s3:PutObjectAcl",
+ "s3:PutObjectVersionAcl",
+ "s3:RestoreObject"
+ ],
+ "Resource": [
+ "arn:aws:s3:::{{ bucket.name }}/*"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/roles/aws-s3/templates/AWS-S3-BucketPolicy.json.j2 b/roles/aws-s3/templates/AWS-S3-BucketPolicy.json.j2
new file mode 100644
index 0000000..fe008ae
--- /dev/null
+++ b/roles/aws-s3/templates/AWS-S3-BucketPolicy.json.j2
@@ -0,0 +1,55 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "statement1",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::{{ S3AWSAccountIDPrompt }}:user/{{ user.results[0].user_meta.created_user.user_name }}"
+ },
+ "Action": [
+ "s3:GetBucketLocation",
+ "s3:ListBucket"
+ ],
+ "Resource": [
+ "arn:aws:s3:::{{ bucket.name }}"
+ ]
+ },
+ {
+ "Sid": "statement4",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {{ cloudfront_origin_access_identity }}"
+ },
+ "Action": [
+ "s3:ListBucket",
+ "s3:GetBucketLocation"
+ ],
+ "Resource": "arn:aws:s3:::{{ bucket.name }}"
+ },
+ {
+ "Sid": "statement3",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {{ cloudfront_origin_access_identity }}"
+ },
+ "Action": "s3:GetObject",
+ "Resource": "arn:aws:s3:::{{ bucket.name }}/*"
+ },
+ {
+ "Sid": "statement2",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::{{ S3AWSAccountIDPrompt }}:user/{{ user.results[0].user_meta.created_user.user_name }}"
+ },
+ "Action": [
+ "s3:GetObject",
+ "s3:PutObject"
+
+ ],
+ "Resource": [
+ "arn:aws:s3:::{{ bucket.name }}/*"
+ ]
+ }
+ ]
+}
diff --git a/roles/aws-s3/templates/AWS-S3-LogBucketPolicy.json.j2 b/roles/aws-s3/templates/AWS-S3-LogBucketPolicy.json.j2
new file mode 100644
index 0000000..5289e3e
--- /dev/null
+++ b/roles/aws-s3/templates/AWS-S3-LogBucketPolicy.json.j2
@@ -0,0 +1,63 @@
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "statement1",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::{{ S3AWSAccountIDPrompt }}:user/{{ user.results[0].user_meta.created_user.user_name }}"
+ },
+ "Action": [
+ "s3:GetBucketLocation",
+ "s3:ListBucket"
+ ],
+ "Resource": [
+ "arn:aws:s3:::{{ logbucket.name }}"
+ ]
+ },
+ {
+ "Sid": "statement4",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {{ cloudfront_origin_access_identity }}"
+ },
+ "Action": [
+ "s3:ListBucket",
+ "s3:GetBucketLocation"
+ ],
+ "Resource": "arn:aws:s3:::{{ logbucket.name }}"
+ },
+ {
+ "Sid": "statement3",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {{ cloudfront_origin_access_identity }}"
+ },
+ "Action": "s3:GetObject",
+ "Resource": "arn:aws:s3:::{{ logbucket.name }}/*"
+ },
+ {
+ "Sid": "statement2",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::{{ S3AWSAccountIDPrompt }}:user/{{ user.results[0].user_meta.created_user.user_name }}"
+ },
+ "Action": [
+ "s3:GetObject",
+ "s3:PutObject"
+ ],
+ "Resource": [
+ "arn:aws:s3:::{{ logbucket.name }}/*"
+ ]
+ },
+ {
+ "Sid": "Stmt1481733330791",
+ "Effect": "Allow",
+ "Principal": {
+ "AWS": "arn:aws:iam::127311923021:root"
+ },
+ "Action": "s3:PutObject",
+ "Resource": "arn:aws:s3:::{{ logbucket.name }}/elb/AWSLogs/{{ S3AWSAccountIDPrompt }}/*"
+ }
+ ]
+}
diff --git a/roles/aws-s3/templates/s3.j2 b/roles/aws-s3/templates/s3.j2
new file mode 100644
index 0000000..2274755
--- /dev/null
+++ b/roles/aws-s3/templates/s3.j2
@@ -0,0 +1,52 @@
+---
+
+# This file was generated by the aws-s3 ansible role.
+
+# Configuration for using Amazon S3 for content storage
+# If you aren't using S3, you can safely ignore this file
+
+# The variable in env.yml called S3FileStorageEnabled must be set to "True" for any of this to have an effect.
+
+S3FileStorageEnabled: true
+S3ObjectStorageEnabled: true
+
+# The Account ID in which your bucket lives
+S3AWSAccountID: {{ S3AWSAccountIDPrompt }}
+
+# If you are using S3 for content storage, this should be an AWS Keypair
+# that has read/write access to the bucket where your content lives
+# To generate IAM and bucket policies suitable for your purposes, please see the README
+# and search for "S3 Support"
+
+S3FileStorageIAMUsername: "{{ S3FileStorageIAMUsernamePrompt }}"
+S3FileStorageAwsId: "{{ S3FileStorageAwsId }}"
+S3FileStorageAwsKey: "{{ S3FileStorageAwsKey }}"
+
+# The name of the bucket in which you'll store your content.
+S3FileStorageBucket: "{{ bucket.name }}"
+
+# A bucket in which we store logs
+S3LogStorageBucket: "{{ logbucket.name }}"
+
+# The S3 region to use.
+S3FileStorageRegion: "us-east-1"
+
+# The S3 endpoint to connect to; should match the region set above, can not contain a custom port
+S3Endpoint: "s3-external-1.amazonaws.com"
+
+# The HTTP port to use for S3 (optional)
+#cc_s3_http_port: 80
+
+# The HTTPS port to use for S3 (optional)
+#cc_s3_https_port: 443
+
+# Force HTTPS for S3 (optional, default `true`)
+#cc_s3_https_only: true
+
+# Disable DNS buckets (see http://www.jets3t.org/toolkit/configuration.html for more info)
+#cc_s3_disable_dns_buckets: false
+
+{% raw %}
+# The temp directory to use for S3 uploads/downloads
+S3FileStorageTempDir: "{{ data_root }}/s3temp"
+{% endraw %}
diff --git a/roles/cc-scorm-engine/defaults/main.yml b/roles/cc-scorm-engine/defaults/main.yml
new file mode 100644
index 0000000..c268be0
--- /dev/null
+++ b/roles/cc-scorm-engine/defaults/main.yml
@@ -0,0 +1,72 @@
+engine_db_name: ScormEngineDB
+engine_db_username: scormengine
+engine_db_url: "jdbc:mysql:{{ engine_db_failover_parameter }}//{{ cc_db_host }}/{{ engine_db_name }}"
+enable_statement_conversion: "False"
+enable_statement_conversion_historical: false
+
+# engine syslog log level
+# Should be one of the following, listed here in descending verbosity:
+# trace, debug, info, warn, error
+# trace and debug will blow up busy servers
+# and info shouldn't be used on busy production boxes if it's avoidable
+engine_log_level: "info"
+
+# Enabling PlayerFramesetAlways will cause the default configuration for any new content/equivalents to be Frameset/Frameset
+PlayerFramesetAlways: false
+
+# Enabling PlayerDisableRightClick will restrict the right-click functionality in the content player window
+PlayerDisableRightClick: false
+
+# Enabling PerserveRunTimeData will change the default value for the "Reset RunTime Data Timing" course configuration setting
+# to "Never", from the regular default of "When Exit is Not Suspend" for SCORM 2004 content. This means that run-time data
+# will not be reset on subsequent launches after a non-suspend exit. This setting can be overridden on a per-course basis
+# from the course's Test & Configure page.
+PreserveRunTimeData: false
+
+# By default, the engine upgrade migrations don't run.
+# specify this at build time if they need to run.
+engine_upgrade: false
+
+engine_use_modern_player: "True"
+
+# Specify a failover parameter. Options are failover:, sequential:, replication:, aurora:
+# https://mariadb.com/kb/en/library/failover-and-high-availability-with-mariadb-connector-j/
+# It should include the end colon, please
+engine_db_failover_parameter: ""
+
+# Set the query used to validate a DB connection. A good reason for changing this would be to check if a connection
+# is read only. Example:
+# SELECT content_controller_connection_validation();
+engine_db_connnection_initialization_query: "/* ping */ -- Engine Health Check"
+
+# Determines whether runtime data received from player should be stored in a lob column or normalized.
+# Should significantly reduce DB utilization, but isn't great if you're running your own reporting queries
+# against the Engine DB.
+engine_store_runtime_data_as_lob: true
+
+## Prior to 2018 Engine was referred to as ScormEngine, and then the name was later changed to RusticiEngine
+war_name: "{{ 'Scorm' if scorm_engine_war_name == True else 'Rustici' }}"
+
+# Used as the value of TinCanStatementForwardWebTimeout in the Engine config file
+xapi_forwarding_timeout: 10000
+# Used as the value of TinCanStatementForwardStmtBatchSize in the Engine config file
+xapi_forwarding_batch_size: 500
+
+engine_custom_log_levels: {}
+
+always_close_top_on_exit: false
+
+tin_can_account_system_page: false
+
+engine_video_player: "{{ is_3p3_or_later | ternary('VideoJs', 'Native') }}"
+
+deep_linking_content_selector_url: "https://{{ ServerName }}/deepLinking/"
+
+# This object should never have a value. Any defaults set by the Content Controller team should be
+# provided in the below engine_custom_settings_defaults dictionary.
+engine_custom_settings: {}
+engine_custom_settings_defaults:
+ DispatchLaunchStateOverride: Learner_Preferred
+ X_MitigateBrokenXapiAgentIds: true
+
+dispatch_scorm_driver_customizations: {}
\ No newline at end of file
diff --git a/roles/cc-scorm-engine/meta/main.yml b/roles/cc-scorm-engine/meta/main.yml
new file mode 100644
index 0000000..abdadb4
--- /dev/null
+++ b/roles/cc-scorm-engine/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+ - { role: java }
+ - { role: content-controller }
diff --git a/roles/cc-scorm-engine/tasks/dispatch_customizations.yml b/roles/cc-scorm-engine/tasks/dispatch_customizations.yml
new file mode 100644
index 0000000..94d379c
--- /dev/null
+++ b/roles/cc-scorm-engine/tasks/dispatch_customizations.yml
@@ -0,0 +1,13 @@
+---
+ - name: "Check if {{item.file}} customization exists"
+ stat:
+ path: "{{item.src_dir}}{{item.file}}"
+ register: customization_exists
+
+ - name: "Deploy {{item.file}} customization if needed"
+ when: customization_exists.stat.exists
+ copy:
+ src: "{{item.src_dir}}{{item.file}}"
+ dest: "{{ item.dest }}"
+ remote_src: yes
+ mode: 0644
\ No newline at end of file
diff --git a/roles/cc-scorm-engine/tasks/main.yml b/roles/cc-scorm-engine/tasks/main.yml
new file mode 100644
index 0000000..252e7e8
--- /dev/null
+++ b/roles/cc-scorm-engine/tasks/main.yml
@@ -0,0 +1,168 @@
+---
+ - name: Install context.xml
+ template:
+ src=context.xml.j2
+ dest="{{ tomcat_base }}/conf/context.xml"
+ mode=0644
+
+ # Pulls the ccupdated value from the content-controller role
+ # If we have updated the zip file, we need to blow away the app
+ - name: Remove ScormEngineInterface webapp
+ file: path="{{ tomcat_base }}/webapps/ScormEngineInterface" state=absent
+
+ - name: Creates ScormEngineInterface directory
+ file: path={{ tomcat_base }}/webapps/ScormEngineInterface state=directory mode=0750 owner=tomcat group=tomcat
+
+ - name: Explode ScormEngineInterface.war
+ unarchive: src="{{ temp_path }}/engine/ScormEngineInterface.war" dest={{ tomcat_base }}/webapps/ScormEngineInterface force=yes copy=no owner=tomcat group=tomcat
+ when: scorm_engine_war_name
+
+ - name: Explode RusticiEngine.war
+ unarchive: src="{{ temp_path }}/engine/RusticiEngine.war" dest={{ tomcat_base }}/webapps/ScormEngineInterface force=yes copy=no owner=tomcat group=tomcat
+ when: rustici_engine_war_name
+
+ # Older versions of CC have two versions of the Jersey jars in the Engine war
+ # This didn't cause issues with previous Tomcat versions, but breaks things in v9. The older versions
+ # are unneeded, so delete them here
+ - name: Remove old jersey dependencies
+ shell: find {{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/lib -regex '.*/jersey-.*-2.25.1.jar' -not -regex '.*/jersey-guava-2.25.1.jar' -exec rm {} \;
+
+ - name: Remove map files
+ shell: rm -f {{ tomcat_base }}/webapps/ScormEngineInterface/defaultui/player/js/*.map
+
+ # Merging these two dictionaries allows for users to specify their own values
+ # to override the defaults without replacing the entire default dictionary.
+ - name: Apply Engine setting customizations
+ set_fact:
+ engine_custom_settings: "{{ engine_custom_settings_defaults | combine(engine_custom_settings) }}"
+
+ - name: Install {{war_name}}EngineSettings.properties file
+ template: src="SCORMEngineSettings.properties.j2" dest="{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/classes/SCORMEngineSettings.properties" mode=0644
+ notify:
+ - restart tomcat
+ - restart httpd
+ when: scorm_engine_war_name
+
+ - name: Install {{war_name}}EngineSettings.properties file
+ template: src="RusticiEngineSettings.properties.j2" dest="{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/classes/RusticiEngineSettings.properties" mode=0644
+ notify:
+ - restart tomcat
+ - restart httpd
+ when: rustici_engine_war_name
+
+ - name: Install web.xml file
+ template: src=web.xml.j2 dest="{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/web.xml" mode=0644
+ notify:
+ - restart tomcat
+ - restart httpd
+
+ - name: Create EngineInstall.xml
+ template:
+ src=EngineInstall.xml.j2
+ dest="{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/EngineInstall.xml"
+ mode=0644
+ when: initialize_mysql
+ notify:
+ - restart tomcat
+ - restart httpd
+
+ - name: Create logback.xml as used by the upgrade tool
+ template:
+ src=logback.upgrade.xml.j2
+ dest="{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/logback.xml"
+ mode=0644
+ when: initialize_mysql
+ notify:
+ - restart tomcat
+ - restart httpd
+
+ - name: Create logback.xml used by Engine proper
+ template:
+ src=logback.engine.xml.j2
+ dest="{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/classes/logback.xml"
+ mode=0644
+ when: initialize_mysql
+ notify:
+ - restart tomcat
+ - restart httpd
+
+ - name: Determine if ScormEngineDB is already installed
+ command: mysql -u {{ mysql_root_user }} -p{{ mysql_root_password }} -h {{ cc_db_host }} --batch --skip-column-names --execute "SELECT COUNT(*) > 0 AS installed FROM information_schema.tables WHERE table_schema = '{{ engine_db_name }}' AND table_name = 'ScormPackage';"
+ register: dbinstalled
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: Install Engine Database Tables (when appropriate)
+ command: "java -cp '{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/lib/*' -Dlogback.configurationFile='{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/logback.xml' RusticiSoftware.ScormContentPlayer.Logic.Upgrade.ConsoleApp {{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/EngineInstall.xml -install"
+ when: initialize_mysql and dbinstalled.stdout == "0"
+ run_once: "{{ run_db_tasks_once }}"
+ notify:
+ - restart tomcat
+ - restart httpd
+
+ - name: Upgrade Engine Database Tables (when appropriate)
+ command: "java -cp '{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/lib/*' -Dlogback.configurationFile='{{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/logback.xml' RusticiSoftware.ScormContentPlayer.Logic.Upgrade.ConsoleApp {{ tomcat_base }}/webapps/ScormEngineInterface/WEB-INF/EngineInstall.xml"
+ when: engine_upgrade is defined and engine_upgrade|bool
+ run_once: "{{ run_db_tasks_once }}"
+ notify:
+ - restart tomcat
+ - restart httpd
+
+ - name: Engine DB repeatable migrations
+ template:
+ src: "{{ item }}"
+ dest: /tmp/R__append_rewrite_param_to_source_url.sql
+ mode: 0644
+ with_items:
+ - R__append_rewrite_param_to_source_url.sql.j2
+ when: cc_run_db_migrations
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: Run Engine DB repeatable migrations
+ mysql_db:
+ login_host: "{{ cc_db_host }}"
+ login_user: "{{ mysql_root_user }}"
+ login_password: "{{ mysql_root_password }}"
+ name: "{{ engine_db_name }}"
+ state: import
+ target: "/tmp/{{ item }}"
+ with_items:
+ - R__append_rewrite_param_to_source_url.sql
+ when: cc_run_db_migrations
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: Clean up default webapps
+ command: "rm -rf {{ tomcat_base }}/webapps/{docs,examples,ROOT}"
+ notify:
+ - restart tomcat
+ - restart httpd
+
+ - name: link scormengine.log into /var/log for convenience
+ file: src="{{ tomcat_base }}/logs/scormengine.log" dest="/var/log/scormengine.log" state=link force=yes
+
+ - name: install logrotate config
+ template:
+ src: scormengine.logrotate.j2
+ dest: /etc/logrotate.d/scormengine
+ owner: root
+ group: root
+ mode: 0644
+
+ - name: Deploy dispatch customizations
+ include_tasks: dispatch_customizations.yml
+ loop:
+ - { src_dir: "{{ temp_path }}/service/dispatch/web/", file: "DispatchHost.html", dest: "{{ tomcat_base }}/webapps/ScormEngineInterface/dispatch/DispatchHost.html" }
+ - { src_dir: "{{ temp_path }}/service/dispatch/web/", file: "Closer.html", dest: "{{ tomcat_base }}/webapps/ScormEngineInterface/dispatch/Closer.html" }
+ - { src_dir: "{{ temp_path }}/service/dispatch/web/", file: "dispatch.client.loader.js", dest: "{{ tomcat_base }}/webapps/ScormEngineInterface/dispatch/dispatch.client.loader.js" }
+ - { src_dir: "{{ temp_path }}/service/dispatch/web/", file: "dispatch.client.driver.js", dest: "{{ tomcat_base }}/webapps/ScormEngineInterface/dispatch/dispatch.client.driver.js" }
+ - { src_dir: "{{ temp_path }}/service/dispatch/player/", file: "dispatch-server-min.js", dest: "{{ tomcat_base }}/webapps/ScormEngineInterface/defaultui/player/js/dispatch-server-min.js" }
+
+ - set_fact:
+ dict: "{{ dispatch_scorm_driver_customizations | combine( { 'EXIT_BEHAVIOR': 'ALWAYS_CLOSE_TOP' } ) }}"
+ when: always_close_top_on_exit
+
+ - name: Customize scormdriver.js if necessary
+ with_items: "{{ dispatch_scorm_driver_customizations }}"
+ replace:
+ path: "{{ tomcat_base }}/webapps/ScormEngineInterface/dispatch/scormdriver.js"
+ regexp: 'var {{ item.name }}=[^;]+;'
+ replace: 'var {{ item.name }}={{ item.value }};'
diff --git a/roles/cc-scorm-engine/templates/EngineInstall.xml.j2 b/roles/cc-scorm-engine/templates/EngineInstall.xml.j2
new file mode 100644
index 0000000..3927bae
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/EngineInstall.xml.j2
@@ -0,0 +1,33 @@
+
+
+
+
+
+ mysql
+ {{ engine_db_url }}?user={{ engine_db_username }}&password={{ engine_db_password }}&useSsl={{ db_use_ssl }}&enabledSslProtocolSuites={{ db_ssl_protocols }}|org.mariadb.jdbc.Driver
+ {{ engine_db_url }}?user={{ engine_db_username }}&password={{ engine_db_password }}&useSsl={{ db_use_ssl }}&enabledSslProtocolSuites={{ db_ssl_protocols }}|org.mariadb.jdbc.Driver
+
+
+ 5000
+ 3
+ True
+ CountUpgradeableRowsPhase,DeleteOrphanedRowsPhase,MigratePackagePropertiesPhase
+ True
+ True
+ {{ content_root }}/xapi
+ {% if S3ObjectStorageEnabled is defined and S3ObjectStorageEnabled %}
+
+ {{ S3FileStorageAwsId }}
+ {{ S3FileStorageAwsKey }}
+ {{ S3FileStorageRegion }}
+ {{ S3FileStorageBucket }}
+
+ {% endif %}
+
diff --git a/roles/cc-scorm-engine/templates/R__append_rewrite_param_to_source_url.sql.j2 b/roles/cc-scorm-engine/templates/R__append_rewrite_param_to_source_url.sql.j2
new file mode 100644
index 0000000..7ea5b3e
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/R__append_rewrite_param_to_source_url.sql.j2
@@ -0,0 +1,13 @@
+DROP PROCEDURE IF EXISTS append_rewrite_to_source_url;
+
+-- Creates a function that updates old source urls for tin can forwarding statements to include xAPI overlay rewrite querey param
+DELIMITER $$
+CREATE PROCEDURE append_rewrite_to_source_url()
+BEGIN
+ UPDATE {{ engine_db_name }}.TinCanForwardingMap
+ SET source_url = CONCAT(source_url, '&rusticiRewrite=true')
+ WHERE source_url NOT LIKE '%rusticiRewrite%';
+END $$
+DELIMITER ;
+
+CALL append_rewrite_to_source_url();
diff --git a/roles/cc-scorm-engine/templates/RusticiEngineSettings.properties.j2 b/roles/cc-scorm-engine/templates/RusticiEngineSettings.properties.j2
new file mode 100644
index 0000000..d43d99c
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/RusticiEngineSettings.properties.j2
@@ -0,0 +1,302 @@
+
+
+
+
+ com.rusticisoftware.contentcontroller.engine.integration.ContentControllerIntegration
+
+ {{ database_connection_string }}
+
+ plugin
+ com.rusticisoftware.contentcontroller.engine.ContentControllerDataHelper
+ ignored
+
+ true
+ v2
+ http://localhost:8989/api/engine/results
+ full
+ {{ engine_user }}
+ {{ engine_password }}
+ false
+ http://localhost:8989/api/engine/registration
+ http://localhost:8989/api/engine/launch
+ system
+
+ /ScormEngineInterface
+ https://{{ ServerName }}/ScormEngineInterface/api
+ /ScormEngineInterface/exit.html
+ /ScormEngineInterface/scripts
+
+ /ScormEngineInterface/exit.html
+ /ScormEngineInterface/scripts
+ True
+
+ /courses
+ {{ courses_filepath }}
+ {{ uploads_filepath }}
+ .*\/[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}\/\d+
+ false
+
+ True
+
+ 2
+ true
+
+ true
+ true
+ true
+ 1048576
+
+ {{ tomcat_base }}/webapps/ScormEngineInterface
+
+{% if cc_version is version('3.3.92', '>=') %}
+ default|600000, configuration|60000,registrationConfiguration|0
+{% endif %}
+
+ TCAPI
+ {{ content_root }}/xapi/
+ {% if S3ObjectStorageEnabled is defined and S3ObjectStorageEnabled %}
+
+ ignored
+ com.rusticisoftware.contentcontroller.engine.ContentControllerTinCanFacilitiesFactory
+ {{ S3FileStorageAwsId }}
+ {{ S3FileStorageAwsKey }}
+ {{ S3FileStorageRegion }}
+ {{ S3FileStorageBucket }}
+
+ {% endif %}
+
+ https://{{ServerName}}/ScormEngineInterface/dispatch
+ false
+ true
+ {{ DispatchPollingFrequency }}
+ Lookahead_Preferred
+
+
+ {{ engine_video_player }}
+ 10
+ 10
+ 0.5,0.75,1,1.25,1.5,2
+
+
+ {% if dispatch_polling_base_url is defined %}
+ {{ dispatch_polling_base_url }}
+ {% endif %}
+
+ {% if PlayerFramesetAlways is defined and PlayerFramesetAlways %}
+ FRAMESET
+ FRAMESET
+ {% endif %}
+
+ {% if PlayerDisableRightClick is defined and PlayerDisableRightClick %}
+ true
+ /api/assets/js/disableRightClick.js
+ {% endif %}
+
+ {% if player_desired_height is defined %}
+ {{ player_desired_height }}
+ {% endif %}
+
+ {% if player_desired_width is defined %}
+ {{ player_desired_width }}
+ {% endif %}
+
+ {% if player_return_to_lms is defined %}
+ {{ player_return_to_lms }}
+ {% endif %}
+
+ {% if player_satisfied_causes_completion is defined and player_satisfied_causes_completion %}
+ YES
+ {% endif %}
+
+
+ {% if player_reset_runtime_data is defined %}
+ {{ player_reset_runtime_data }}
+ {% endif %}
+
+ \QiPhone\E|\QAndroid\E|\QWindows Phone\E|\QMobile\E|\QOperaMini\E|\QTablet\E
+ {% if ForceFramesetMobile is defined and ForceFramesetMobile %}
+ true
+ {% else %}
+ false
+ {% endif %}
+
+ {% if PreserveRunTimeData is defined and PreserveRunTimeData %}
+ NEVER
+ {% endif %}
+
+ False
+ SEPARATE
+ False
+ {{ engine_store_runtime_data_as_lob }}
+
+ True
+ {{ engine_use_modern_player }}
+ false
+ /api/assets/css/cc-player.min.css?build_version={{ build_name.split('-')[1] }}
+ /api/assets/css/cc-player.min.css?build_version={{ build_name.split('-')[1] }}
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ False
+ True
+
+ False
+ 1
+ 3000
+
+ False
+
+ False
+
+ True
+
+ {{ DefaultCommCommitFrequency }}
+
+
+
+
+
+
+
+
+ 2500000
+
+
+
+
+ https://{{ ServerName }}{% if tin_can_account_system_page %}/${TenantName}{%endif%}
+
+ false
+
+ False
+ 1.0.0
+ {{enable_statement_conversion}}
+ {{enable_statement_conversion_historical}}
+
+
+
+
+
+
+
+ True
+ 60000
+ 24
+ {{ xapi_forwarding_batch_size }}
+ {{ xapi_forwarding_timeout }}
+
+
+ AttachmentContentTransferEncoding, SignatureContentType, IdenticalDocumentsCanConflict, StatementSchemaValidation, NewDocumentsRequireETag
+
+
+
+ False
+ 60000
+
+
+
+
+ {{ tomcat_base }}/webapps/ScormEngineInterface/recipes.json
+
+
+
+ {{ engine_user }} : {{ engine_password }} : read-only
+ {{ engine_user }} : {{ engine_password }} : root
+
+
+
+ {{ engine_user }} : {{ engine_password }} : root
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% for setting_name, setting_value in engine_custom_settings.items() %}
+ {{ setting_value }}
+ {% endfor %}
+
+ {% if deep_linking_content_selector_url is defined %}
+ {{ deep_linking_content_selector_url }}
+ {% endif %}
+
+ {% if deep_linking_secret is defined %}
+ {{ deep_linking_secret }}
+ {% endif %}
+
+ {% if enable_engine_metrics is defined %}
+ {{ enable_engine_metrics }}
+ {% endif %}
+
diff --git a/roles/cc-scorm-engine/templates/SCORMEngineSettings.properties.j2 b/roles/cc-scorm-engine/templates/SCORMEngineSettings.properties.j2
new file mode 100644
index 0000000..2fe82bd
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/SCORMEngineSettings.properties.j2
@@ -0,0 +1,245 @@
+
+
+
+
+ com.rusticisoftware.contentcontroller.engine.integration.ContentControllerIntegration
+
+ {{ database_connection_string }}
+
+ plugin
+ com.rusticisoftware.contentcontroller.engine.ContentControllerDataHelper
+ ignored
+
+ http://localhost:8989/api/engine/results
+ full
+ {{ engine_user }}
+ {{ engine_password }}
+ false
+ http://localhost:8989/api/engine/registration
+ http://localhost:8989/api/engine/launch
+
+ /ScormEngineInterface
+ /ScormEngineInterface/exit.html
+ /ScormEngineInterface/scripts
+
+ /ScormEngineInterface/exit.html
+ /ScormEngineInterface/scripts
+ True
+
+ /courses
+ {{ courses_filepath }}
+ {{ uploads_filepath }}
+ false
+
+ True
+
+ 2
+ true
+
+ true
+ true
+ true
+ 1048576
+
+ {{ tomcat_base }}/webapps/ScormEngineInterface
+
+ {{ content_root }}/xapi/
+ {% if S3ObjectStorageEnabled is defined and S3ObjectStorageEnabled %}
+
+ ignored
+ com.rusticisoftware.contentcontroller.engine.ContentControllerTinCanFacilitiesFactory
+ {{ S3FileStorageAwsId }}
+ {{ S3FileStorageAwsKey }}
+ {{ S3FileStorageRegion }}
+ {{ S3FileStorageBucket }}
+
+ {% endif %}
+
+ https://{{ServerName}}/ScormEngineInterface/dispatch
+
+ {% if PlayerFramesetAlways is defined and PlayerFramesetAlways %}
+ FRAMESET
+ FRAMESET
+ {% endif %}
+
+ {% if PlayerDisableRightClick is defined and PlayerDisableRightClick %}
+ YES
+ /api/assets/js/disableRightClick.js
+ {% endif %}
+
+ {% if PreserveRunTimeData is defined and PreserveRunTimeData %}
+ NEVER
+ {% endif %}
+
+ {% if player_desired_height is defined %}
+ {{ player_desired_height }}
+ {% endif %}
+
+ {% if player_desired_width is defined %}
+ {{ player_desired_width }}
+ {% endif %}
+
+ {{ engine_store_runtime_data_as_lob }}
+
+ True
+ {{ engine_use_modern_player }}
+ /api/assets/css/cc-player.min.css?build_version={{ build_name.split('-')[1] }}
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ False
+ True
+
+ False
+ 1
+ 3000
+
+ False
+
+ False
+
+ True
+
+ {{ DefaultCommCommitFrequency }}
+
+
+
+
+
+
+
+
+ 2500000
+
+
+
+
+ https://{{ ServerName }}
+
+ false
+
+ False
+ 1.0.0
+ {{enable_statement_conversion}}
+ False
+
+
+
+
+
+
+
+ True
+ 60000
+ 24
+ 500
+ 1000
+
+
+ AttachmentContentTransferEncoding, SignatureContentType, IdenticalDocumentsCanConflict, StatementSchemaValidation, NewDocumentsRequireETag
+
+
+
+ False
+ 60000
+
+
+
+
+ {{ tomcat_base }}/webapps/ScormEngineInterface/recipes.json
+
+
+
+ {{ engine_user }} : {{ engine_password }} : read-only
+ {{ engine_user }} : {{ engine_password }} : root
+
+
+
+ {{ engine_user }} : {{ engine_password }} : root
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if deep_linking_content_selector_url is defined %}
+ {{ deep_linking_content_selector_url }}
+ {% endif %}
+
+ {% if deep_linking_secret is defined %}
+ {{ deep_linking_secret }}
+ {% endif %}
+
+
diff --git a/roles/cc-scorm-engine/templates/context.xml.j2 b/roles/cc-scorm-engine/templates/context.xml.j2
new file mode 100644
index 0000000..1e34243
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/context.xml.j2
@@ -0,0 +1,34 @@
+
+
+ WEB-INF/web.xml
+
+
+
diff --git a/roles/cc-scorm-engine/templates/logback.engine.xml.j2 b/roles/cc-scorm-engine/templates/logback.engine.xml.j2
new file mode 100644
index 0000000..188a759
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/logback.engine.xml.j2
@@ -0,0 +1,24 @@
+
+
+ ${catalina.home:-.}/logs/scormengine.log
+
+
+ %-5level %date{yyyy-MM-dd HH:mm:ss,UTC} UTC – %m%n
+
+
+ ${catalina.home:-.}/logs/scormengine.%i.log
+ 1
+ 3
+
+
+ 10MB
+
+
+ {% for logger_name, log_level in engine_custom_log_levels.items() %}
+
+ {% endfor %}
+
+
+
+
diff --git a/roles/cc-scorm-engine/templates/logback.upgrade.xml.j2 b/roles/cc-scorm-engine/templates/logback.upgrade.xml.j2
new file mode 100644
index 0000000..7d308a4
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/logback.upgrade.xml.j2
@@ -0,0 +1,27 @@
+
+
+
+
+ INFO
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+ engine_upgrade.log
+ true
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
diff --git a/roles/cc-scorm-engine/templates/scormengine.logrotate.j2 b/roles/cc-scorm-engine/templates/scormengine.logrotate.j2
new file mode 100644
index 0000000..0859fb6
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/scormengine.logrotate.j2
@@ -0,0 +1,9 @@
+/var/log/scormengine.log {
+ rotate 7
+ daily
+ missingok
+ notifempty
+ sharedscripts
+ copytruncate
+ compress
+}
diff --git a/roles/cc-scorm-engine/templates/web.xml.j2 b/roles/cc-scorm-engine/templates/web.xml.j2
new file mode 100644
index 0000000..297e4f5
--- /dev/null
+++ b/roles/cc-scorm-engine/templates/web.xml.j2
@@ -0,0 +1,210 @@
+
+
+
+ RusticiSoftware.ScormContentPlayer.Interface
+
+
+ com.rusticisoftware.scormengine.Initializer
+
+
+
+ java.lang.Throwable
+ /error-pages/error.jsp
+
+
+
+ 500
+ /error-pages/error.jsp
+
+
+
+ 404
+ /error-pages/404.jsp
+
+
+
+ SCORM Engine DB Connection
+ jdbc/{{ engine_db_name }}
+ javax.sql.DataSource
+ Container
+
+
+
+ TCAPI Servlet
+ TCAPIServlet
+ com.rusticisoftware.tincan.TCAPIServlet
+
+
+ TCAPIServlet
+ /TCAPI/*
+
+
+ TCAPIServlet
+ /Recipes/*
+
+
+
+ Metrics Servlet
+ MetricsReportServlet
+ RusticiSoftware.ScormContentPlayer.Util.Metrics.MetricsReportServlet
+
+
+ MetricsReportServlet
+ /metrics
+
+
+
+ Helper for Launch Report
+ ReportsHelper
+ ReportsHelper
+ com.rusticisoftware.scormengine.ReportsHelper
+
+
+ ReportsHelper
+ /reportsHelper
+
+
+
+ SnaffleRequest
+ RusticiSoftware.ScormContentPlayer.Util.SnaffleRequestFilter
+
+
+
+ SnaffleRequest
+ /*
+
+
+
+ HttpMetricsFilter
+ RusticiSoftware.ScormContentPlayer.Util.HttpMetricsFilter
+
+
+ HttpMetricsFilter
+ /*
+
+
+
+ api
+
+ org.glassfish.jersey.servlet.ServletContainer
+
+
+ javax.ws.rs.Application
+{% if scorm_engine_war_name %}
+ com.rusticisoftware.scormengine.dispatch.api.DispatchApiApplication
+{% else %}
+ RusticiSoftware.Engine.api.ApiApplication
+{% endif %}
+
+
+ jersey.config.server.provider.packages
+ com.fasterxml.jackson.jaxrs.json
+
+
+
+ api
+ /api/v1/*
+
+
+
+ apiv2
+ org.glassfish.jersey.servlet.ServletContainer
+
+ jersey.config.server.provider.packages
+
+ RusticiSoftware.Engine.api.v2,
+ RusticiSoftware.Engine.api.util,
+ com.fasterxml.jackson.jaxrs.json
+
+
+
+ jersey.config.server.provider.classnames
+
+ org.glassfish.jersey.media.multipart.MultiPartFeature,
+ RusticiSoftware.Engine.api.v2.ApiParamConverterProvider,
+ RusticiSoftware.Engine.api.ApiExceptionMapper,
+ RusticiSoftware.Engine.api.ApiLoggingFilter
+
+
+
+ jersey.config.server.wadl.disableWadl
+ true
+
+ 1
+
+
+ apiv2
+ /api/v2/*
+
+
+
+ RemoteIpFilter
+ org.apache.catalina.filters.RemoteIpFilter
+
+ protocolHeader
+ x-forwarded-proto
+
+
+
+ RemoteIpFilter
+ /*
+ REQUEST
+
+
+ CorsFilter
+ org.apache.catalina.filters.CorsFilter
+
+ cors.allowed.origins
+ *
+
+
+ cors.allowed.methods
+ GET,POST,HEAD,OPTIONS,PUT
+
+
+ cors.allowed.headers
+ Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers
+
+
+ cors.exposed.headers
+ Access-Control-Allow-Origin,Access-Control-Allow-Credentials
+
+
+ cors.support.credentials
+
+ false
+
+
+ cors.preflight.maxage
+ 10
+
+
+
+ CorsFilter
+ /*
+
+
+
+
+
+ *.jsp
+ true
+
+
+
diff --git a/roles/cc-scorm-engine/vars/main.yml b/roles/cc-scorm-engine/vars/main.yml
new file mode 100644
index 0000000..d57e980
--- /dev/null
+++ b/roles/cc-scorm-engine/vars/main.yml
@@ -0,0 +1,70 @@
+---
+
+#mysql
+#mysql_jdbc_driver_url: http://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.35.zip
+
+tomcat_base: /usr/share/tomcat
+# using false here makes ansible look for a ScormEngine package (unzipped release package) in the deploy folder
+# using true will make ansible download the release specified in the engine_package_url var
+download_release: true
+
+# location for ScormEngine package files (Database, ScormEngineInterface.war, etc...)
+deploy_path: /deploy/
+engine_package_path: /deploy/ScormEngine
+
+use_db_installer: true
+use_autotest_integration: false
+
+# This setting can have a powerful effect on database usage in busy environments.
+# It controls how frequently a course calls RecordResults.jsp
+# In milliseconds
+# Default value is 10000
+# Suggest value for a busy environment is 20000
+DefaultCommCommitFrequency: 10000
+
+# The frequency (in milliseconds) the dispatch package will poll back to Engine
+# for an updated registration state. This setting will be used only by non-SCORM
+# content packages.
+#
+# Default value is 1500 milliseconds
+DispatchPollingFrequency: 1500
+
+ApiRollupRegistrationAuthUser:
+ApiRollupRegistrationAuthPassword:
+#tomcat settings
+interface_filepath: "{{ tomcat_base }}/webapps/ScormEngineInterface"
+
+# interface_webpath is deprecated
+# interface_webpath: {{ ServerName }}/ScormEngineInterface
+content_root: "{{ data_root }}/content"
+courses_filepath: "{{ content_root }}/courses"
+courses_webpath: /courses
+uploads_filepath: "{{ content_root }}/uploads"
+
+redirect_on_exit_url: /ScormEngineInterface/tools/console/index.html
+autotest_redirect_on_exit_url: /courses/autotest/closer.html
+
+# We only support MySQL 5.6 for Content Controller
+data_persistence_engine: mysql
+
+#RusticiEngineSettings.properties
+
+database_connection_string: jdbc/{{ engine_db_name }}
+integration_class_name: RusticiSoftware.ScormContentPlayer.api.implementation.ApiIntegration
+autotest_integration_class_name: RusticiSoftware.ScormEngine.AutoTestIntegration.AutoTestIntegration
+
+# system_homepage_url is deprecated
+# system_homepage_url: {{ ServerName }}
+
+enable_scorm_to_tincan: true
+enable_recipes: true
+recipes_sleeptime: 60000
+
+#jenkins slave
+install_jenkins_swarm: false
+jenkins_home: /jenkins
+jenkins_slave_name: vagrant_slave_ubuntu
+
+#testing
+run_scormformance: false
+run_canformance: false
diff --git a/roles/cloudfront/defaults/main.yml b/roles/cloudfront/defaults/main.yml
new file mode 100644
index 0000000..c6e91b5
--- /dev/null
+++ b/roles/cloudfront/defaults/main.yml
@@ -0,0 +1,21 @@
+---
+
+# Enable the things. Note that if you aren't also using S3 for file storage,
+# CloudFront isn't going to do anything useful for you
+use_cloudfront: false
+
+# You'll use the access key ID when you create signed URLs or signed cookies.
+cloudfront_access_key_id:
+
+# Distribution Domain - the domain name of your CloudFront distribution
+# This should always be the same as {{ ServerName }}, and we're only adding it here
+# to be really explicit and stupidly verbose.
+cloudfront_distro_domain: "{{ ServerName }}"
+
+# Generate your keys via this process:
+# http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html
+# You'll need to run openssl pkcs8 -topk8 -nocrypt -in privatekey.pem -inform PEM -out privatekey.der -outform DER
+# against your private key to make it work with Java.
+# Place your private key and public key in "roles/cloudfront/files"
+cloudfront_private_key: privatekey.der
+cloudfront_public_key: rsa-publickey.pem
diff --git a/roles/cloudfront/meta/main.yml b/roles/cloudfront/meta/main.yml
new file mode 100644
index 0000000..48594e4
--- /dev/null
+++ b/roles/cloudfront/meta/main.yml
@@ -0,0 +1,4 @@
+---
+
+dependencies:
+- content-controller
diff --git a/roles/cloudfront/tasks/main.yml b/roles/cloudfront/tasks/main.yml
new file mode 100644
index 0000000..7bc3a23
--- /dev/null
+++ b/roles/cloudfront/tasks/main.yml
@@ -0,0 +1,16 @@
+---
+
+ - name: Copy over der file
+ copy: src={{ cloudfront_private_key }} dest={{ cc_deploy_path }}/bin/{{ cloudfront_private_key }} owner=root group=tomcat mode=0640
+ when: use_cloudfront is defined and use_cloudfront|bool == True
+
+
+ - name: Copy over pem public key
+ copy: src={{ cloudfront_public_key }} dest={{ cc_deploy_path }}/bin/{{ cloudfront_public_key }} owner=root group=tomcat mode=0640
+ when: use_cloudfront is defined and use_cloudfront|bool == True
+
+
+ - name: Include local content proxy
+ include_role:
+ name: local-content-proxy
+ when: content_proxy_enabled is defined and content_proxy_enabled|bool == True
diff --git a/roles/cloudfront/vars/main.yml b/roles/cloudfront/vars/main.yml
new file mode 100644
index 0000000..ed97d53
--- /dev/null
+++ b/roles/cloudfront/vars/main.yml
@@ -0,0 +1 @@
+---
diff --git a/roles/common/defaults/main.yml b/roles/common/defaults/main.yml
new file mode 100644
index 0000000..d68b51b
--- /dev/null
+++ b/roles/common/defaults/main.yml
@@ -0,0 +1,20 @@
+---
+
+env: dev
+
+# Use a local apt-cacher server to fetch packages
+use_local_apt_cache: false
+local_apt_cache_host: ""
+
+noupgrade: false
+
+# If you're running on an AWS instance and set this to true,
+# ansible will install the AWS inspector agent for you
+# setting this to true on a non-AWS ec2 box will have no effect.
+aws_inspector: false
+
+# Old version of MySQL used here to support folks staying on 5.7.
+# New installations who want to use 8 should override this value in group_vars/env.yml
+mysql_version: "5.7"
+
+building_ami: false
diff --git a/roles/common/files/epel-release-latest-7.noarch.rpm b/roles/common/files/epel-release-latest-7.noarch.rpm
new file mode 100644
index 0000000..35ec43c
Binary files /dev/null and b/roles/common/files/epel-release-latest-7.noarch.rpm differ
diff --git a/roles/common/files/epel-release-latest-8.noarch.rpm b/roles/common/files/epel-release-latest-8.noarch.rpm
new file mode 100644
index 0000000..6d43508
Binary files /dev/null and b/roles/common/files/epel-release-latest-8.noarch.rpm differ
diff --git a/roles/common/files/inspector.gpg b/roles/common/files/inspector.gpg
new file mode 100644
index 0000000..3763aae
Binary files /dev/null and b/roles/common/files/inspector.gpg differ
diff --git a/roles/common/files/mysql-community.repo b/roles/common/files/mysql-community.repo
new file mode 100644
index 0000000..f6ecfea
--- /dev/null
+++ b/roles/common/files/mysql-community.repo
@@ -0,0 +1,17 @@
+[mysql57-community]
+name=MySQL 5.7 Community Server
+baseurl=https://repo.mysql.com/yum/mysql-5.7-community/el/7/$basearch/
+enabled=1
+gpgcheck=0
+
+[mysql-connectors-community]
+name=MySQL Connectors Community
+baseurl=https://repo.mysql.com/yum/mysql-connectors-community/el/7/$basearch/
+enabled=1
+gpgcheck=0
+
+[mysql-tools-community]
+name=MySQL Tools Community
+baseurl=https://repo.mysql.com/yum/mysql-tools-community/el/7/$basearch/
+enabled=1
+gpgcheck=0
\ No newline at end of file
diff --git a/roles/common/files/mysql-releases.key b/roles/common/files/mysql-releases.key
new file mode 100644
index 0000000..0125486
--- /dev/null
+++ b/roles/common/files/mysql-releases.key
@@ -0,0 +1,49 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: SKS 1.1.6
+Comment: Hostname: pgp.mit.edu
+
+mQINBGG4urcBEACrbsRa7tSSyxSfFkB+KXSbNM9rxYqoB78u107skReefq4/+Y72TpDvlDZL
+mdv/lK0IpLa3bnvsM9IE1trNLrfi+JES62kaQ6hePPgn2RqxyIirt2seSi3Z3n3jlEg+mSdh
+AvW+b+hFnqxo+TY0U+RBwDi4oO0YzHefkYPSmNPdlxRPQBMv4GPTNfxERx6XvVSPcL1+jQ4R
+2cQFBryNhidBFIkoCOszjWhm+WnbURsLheBp757lqEyrpCufz77zlq2gEi+wtPHItfqsx3rz
+xSRqatztMGYZpNUHNBJkr13npZtGW+kdN/xu980QLZxN+bZ88pNoOuzD6dKcpMJ0LkdUmTx5
+z9ewiFiFbUDzZ7PECOm2g3veJrwr79CXDLE1+39Hr8rDM2kDhSr9tAlPTnHVDcaYIGgSNIBc
+YfLmt91133klHQHBIdWCNVtWJjq5YcLQJ9TxG9GQzgABPrm6NDd1t9j7w1L7uwBvMB1wgpir
+RTPVfnUSCd+025PEF+wTcBhfnzLtFj5xD7mNsmDmeHkF/sDfNOfAzTE1v2wq0ndYU60xbL6/
+yl/Nipyr7WiQjCG0m3WfkjjVDTfs7/DXUqHFDOu4WMF9v+oqwpJXmAeGhQTWZC/QhWtrjrNJ
+AgwKpp263gDSdW70ekhRzsok1HJwX1SfxHJYCMFs2aH6ppzNsQARAQABtDZNeVNRTCBSZWxl
+YXNlIEVuZ2luZWVyaW5nIDxteXNxbC1idWlsZEBvc3Mub3JhY2xlLmNvbT6JAlQEEwEIAD4W
+IQSFm+jXxYb1OEMLGcJGe5QtOnm9KQUCYbi6twIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgID
+AQIeAQIXgAAKCRBGe5QtOnm9KUewD/992sS31WLGoUQ6NoL7qOB4CErkqXtMzpJAKKg2jtBG
+G3rKE1/0VAg1D8AwEK4LcCO407wohnH0hNiUbeDck5x20pgS5SplQpuXX1K9vPzHeL/WNTb9
+8S3H2Mzj4o9obED6Ey52tTupttMF8pC9TJ93LxbJlCHIKKwCA1cXud3GycRN72eqSqZfJGds
+aeWLmFmHf6oee27d8XLoNjbyAxna/4jdWoTqmp8oT3bgv/TBco23NzqUSVPi+7ljS1hHvcJu
+oJYqaztGrAEf/lWIGdfl/kLEh8IYx8OBNUojh9mzCDlwbs83CBqoUdlzLNDdwmzu34Aw7xK1
+4RAVinGFCpo/7EWoX6weyB/zqevUIIE89UABTeFoGih/hx2jdQV/NQNthWTW0jH0hmPnajBV
+AJPYwAuO82rx2pnZCxDATMn0elOkTue3PCmzHBF/GT6c65aQC4aojj0+Veh787QllQ9FrWbw
+nTz+4fNzU/MBZtyLZ4JnsiWUs9eJ2V1g/A+RiIKu357Qgy1ytLqlgYiWfzHFlYjdtbPYKjDa
+ScnvtY8VO2Rktm7XiV4zKFKiaWp+vuVYpR0/7Adgnlj5Jt9lQQGOr+Z2VYx8SvBcC+by3XAt
+YkRHtX5u4MLlVS3gcoWfDiWwCpvqdK21EsXjQJxRr3dbSn0HaVj4FJZX0QQ7WZm6WLkCDQRh
+uLq3ARAA6RYjqfC0YcLGKvHhoBnsX29vy9Wn1y2JYpEnPUIB8X0VOyz5/ALv4Hqtl4THkH+m
+mMuhtndoq2BkCCk508jWBvKS1S+Bd2esB45BDDmIhuX3ozu9Xza4i1FsPnLkQ0uMZJv30ls2
+pXFmskhYyzmo6aOmH2536LdtPSlXtywfNV1HEr69V/AHbrEzfoQkJ/qvPzELBOjfjwtDPDeP
+iVgW9LhktzVzn/BjO7XlJxw4PGcxJG6VApsXmM3t2fPN9eIHDUq8ocbHdJ4en8/bJDXZd9eb
+QoILUuCg46hE3p6nTXfnPwSRnIRnsgCzeAz4rxDR4/Gv1Xpzv5wqpL21XQi3nvZKlcv7J1IR
+VdphK66De9GpVQVTqC102gqJUErdjGmxmyCA1OOORqEPfKTrXz5YUGsWwpH+4xCuNQP0qmre
+Rw3ghrH8potIr0iOVXFic5vJfBTgtcuEB6E6ulAN+3jqBGTaBML0jxgj3Z5VC5HKVbpg2DbB
+/wMrLwFHNAbzV5hj2Os5Zmva0ySP1YHB26pAW8dwB38GBaQvfZq3ezM4cRAo/iJ/GsVE98dZ
+EBO+Ml+0KYj+ZG+vyxzo20sweun7ZKT+9qZM90f6cQ3zqX6IfXZHHmQJBNv73mcZWNhDQOHs
+4wBoq+FGQWNqLU9xaZxdXw80r1viDAwOy13EUtcVbTkAEQEAAYkCPAQYAQgAJhYhBIWb6NfF
+hvU4QwsZwkZ7lC06eb0pBQJhuLq3AhsMBQkDwmcAAAoJEEZ7lC06eb0pSi8P/iy+dNnxrtiE
+Nn9vkkA7AmZ8RsvPXYVeDCDSsL7UfhbS77r2L1qTa2aB3gAZUDIOXln51lSxMeeLtOequLME
+V2Xi5km70rdtnja5SmWfc9fyExunXnsOhg6UG872At5CGEZU0c2Nt/hlGtOR3xbt3O/Uwl+d
+ErQPA4BUbW5K1T7OC6oPvtlKfF4bGZFloHgt2yE9YSNWZsTPe6XJSapemHZLPOxJLnhs3VBi
+rWE31QS0bRl5AzlO/fg7ia65vQGMOCOTLpgChTbcZHtozeFqva4IeEgE4xN+6r8WtgSYeGGD
+RmeMEVjPM9dzQObf+SvGd58u2z9f2agPK1H32c69RLoA0mHRe7Wkv4izeJUc5tumUY0e8Ojd
+enZZjT3hjLh6tM+mrp2oWnQIoed4LxUw1dhMOj0rYXv6laLGJ1FsW5eSke7ohBLcfBBTKnMC
+BohROHy2E63Wggfsdn3UYzfqZ8cfbXetkXuLS/OM3MXbiNjg+ElYzjgWrkayu7yLakZx+mx6
+sHPIJYm2hzkniMG29d5mGl7ZT9emP9b+CfqGUxoXJkjs0gnDl44bwGJ0dmIBu3ajVAaHODXy
+Y/zdDMGjskfEYbNXCAY2FRZSE58tgTvPKD++Kd2KGplMU2EIFT7JYfKhHAB5DGMkx92HUMid
+sTSKHe+QnnnoFmu4gnmDU31i
+=Xqbo
+-----END PGP PUBLIC KEY BLOCK-----
\ No newline at end of file
diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml
new file mode 100644
index 0000000..b4e35de
--- /dev/null
+++ b/roles/common/handlers/main.yml
@@ -0,0 +1,13 @@
+---
+
+ # handlers
+ - name: restart syslog
+ service: name=rsyslog state=restarted
+
+ - name: restart ssh
+ service: name=ssh state=restarted
+ when: ansible_distribution == 'Ubuntu'
+
+ - name: restart sshd
+ service: name=sshd state=restarted
+ when: ansible_distribution == 'CentOS' or ansible_distribution == 'Red Hat Enterprise Linux'
diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml
new file mode 100644
index 0000000..fbf6a1f
--- /dev/null
+++ b/roles/common/tasks/main.yml
@@ -0,0 +1,151 @@
+---
+ - name: Gather heap size settings
+ set_fact:
+ engine_heap_size_sanitized: "{{ engine_heap_size | regex_replace('[gG]', '000') | regex_replace('[mM]', '') }}"
+ cc_heap_size_sanitized: "{{ cc_heap_size | regex_replace('[gG]', '000') | regex_replace('[mM]', '') }}"
+
+ - name: Validate heap size settings
+ fail:
+ msg: "cc_heap_size and engine_heap_size are set to a combined size of {{ cc_heap_size_sanitized|int + engine_heap_size_sanitized|int }} MB, which is more than the available {{ ansible_memory_mb.real.total }} MB on the server "
+ when: (cc_heap_size_sanitized|int + engine_heap_size_sanitized|int > ansible_memory_mb.real.total) and not building_ami
+
+ - name: Validate deep linking secret setting
+ fail:
+ msg: "deep_linking_secret is not defined. The configuration setting must be configured for CC 3.3+ installations."
+ when: is_3p3_or_later|bool == true and deep_linking_secret is not defined
+
+ - name: Gather AWS EC2 instance facts
+ ec2_metadata_facts:
+ ignore_errors: true
+
+ - name: Say what we are up to
+ debug: msg="Environment is {{ env }}. Server name is {{ ServerName }}. Operating system is {{ ansible_os_family}} / {{ ansible_distribution }}."
+
+ - name: Include OS-specific variables
+ include_vars: "{{ ansible_os_family }}.yml"
+
+ - name: Run OS repository setup tasks
+ include_tasks: "repos-{{ ansible_os_family }}.yml"
+
+ - name: Install packages
+ package:
+ name: "{{ common_packages + common_platform_packages }}"
+ state: latest
+ update_cache: yes
+
+ # Packages we don't need for RH 8, but do need for Debian/RH 7
+ - name: Install ntp
+ package:
+ name: ntp
+ state: latest
+ update_cache: yes
+ when: ansible_os_family == 'Debian' or ansible_distribution_major_version == "7"
+
+ # MySQL-python only works on RH 7
+ - name: Install MySQL-python
+ package:
+ name: "MySQL-python"
+ state: latest
+ update_cache: yes
+ when: ansible_os_family == 'RedHat' and ansible_distribution_major_version == "7"
+
+ - name: Install pip3
+ package:
+ name: python3-pip
+ state: present
+ when: ansible_os_family == 'Debian'
+
+ - name: Sleep for a few seconds to allow the previous command to finish its business
+ pause: seconds=5
+
+ - name: install boto
+ pip:
+ name:
+ - boto3==1.17.112
+ - botocore==1.20.112
+ state: latest
+
+ - name: install futures if necessary
+ pip:
+ name:
+ - futures
+ state: latest
+ # This is expected to fail when run using Python 3, but we need this to support Python 2 installation
+ ignore_errors: true
+
+ - name: install PyMySQL
+ pip:
+ name:
+ - PyMySQL
+ state: latest
+ when: ansible_os_family == 'RedHat' and ansible_distribution_major_version == "8"
+
+ - name: Install cronjobs
+ # Common cronjobs will run out of /etc/cron.d/ansible-jobs
+ cron: name="update NTP" cron_file="ansible-jobs" user=root minute="0" hour="10" job="/usr/sbin/ntpdate -u pool.ntp.org"
+ # ntp is not supported on RedHat/CentOS 8
+ when: ansible_os_family == 'Debian' or ansible_distribution_major_version == "7"
+
+ - name: Set timezone to UTC
+ action: shell echo Etc/UTC > /etc/timezone
+
+ - name: Set localtime to UTC
+ file: src=/usr/share/zoneinfo/Etc/UTC dest=/etc/localtime
+
+ - name: Reconfigure tzdata
+ action: command dpkg-reconfigure -f noninteractive tzdata
+ when: ansible_os_family == 'Debian'
+
+ - name: MIME Types File
+ template: src=mime.types dest=/etc/mime.types mode=0644
+
+ - name: Disable reverse DNS lookups on the SSH daemon
+ lineinfile:
+ dest: /etc/ssh/sshd_config
+ regexp: ^UseDNS
+ line: UseDNS no
+ state: present
+ notify:
+ - restart ssh
+ - restart sshd
+
+ - name: Copy over Amazon Inspector GPG key
+ copy:
+ src: inspector.gpg
+ dest: /root/inspector.gpg
+ mode: 0400
+ owner: root
+ group: root
+ when: ansible_ec2_instance_type is defined and ( aws_inspector is defined and aws_inspector|bool == true )
+
+ - name: import Amazon GPG key
+ command: gpg --import /root/inspector.gpg
+ register: gpgimport
+ when: ansible_ec2_instance_type is defined and ( aws_inspector is defined and aws_inspector|bool == true )
+
+ - name: fetch the amazon sig file
+ get_url:
+ url: https://d1wk0tztpsntt1.cloudfront.net/linux/latest/install.sig
+ dest: /root/install.sig
+ when: ansible_ec2_instance_type is defined and ( aws_inspector is defined and aws_inspector|bool == true )
+
+ - name: fetch the amazon inspector install file
+ get_url:
+ url: https://d1wk0tztpsntt1.cloudfront.net/linux/latest/install
+ dest: /root/install
+ when: ansible_ec2_instance_type is defined and ( aws_inspector is defined and aws_inspector|bool == true )
+
+ # If this fails, it exits(1) and kills the ansible build, which is desired, as if it fails it means the inspector binary is tainted
+ - name: verify the sig file
+ command: gpg --verify /root/install.sig
+ register: sigfile
+ when: ansible_ec2_instance_type is defined and ( aws_inspector is defined and aws_inspector|bool == true )
+
+ - name: install the AWS inspector agent
+ command: bash /root/install
+ become: yes
+ when: ansible_ec2_instance_type is defined and ( aws_inspector is defined and aws_inspector|bool == true )
+
+ - name: disable unattended upgrades
+ apt: name=unattended-upgrades state=absent purge=true
+ when: disable_unattended_upgrades is defined and disable_unattended_upgrades|bool == true
diff --git a/roles/common/tasks/repos-Debian.yml b/roles/common/tasks/repos-Debian.yml
new file mode 100644
index 0000000..8038ec6
--- /dev/null
+++ b/roles/common/tasks/repos-Debian.yml
@@ -0,0 +1,103 @@
+---
+- name: Use local apt cache if desired
+ template: src=90-apt-proxy.conf.j2 dest=/etc/apt/apt.conf.d/90-apt-proxy.conf mode=0644
+ when: use_local_apt_cache
+
+- name: Remove local apt cache if disabled
+ file: dest=/etc/apt/apt.conf.d/90-apt-proxy.conf state=absent
+ when: not use_local_apt_cache
+
+- name: Copy apt release signing key for MySQL
+ copy:
+ src: "mysql-releases.key"
+ dest: "/tmp/mysql-releases.key"
+ mode: 0600
+ force: true
+
+- name: Add apt release signing key for MySQL
+ apt_key:
+ file: "/tmp/mysql-releases.key"
+ state: present
+
+# Expires 02/17/2019
+- name: Copy apt release signing key for MySQL
+ copy:
+ src: "mysql-releases.key"
+ dest: "/tmp/mysql-releases.key"
+ mode: 0600
+ force: true
+
+- name: Add apt release signing key for MySQL
+ apt_key:
+ file: "/tmp/mysql-releases.key"
+ state: present
+
+- name: Cleanup MySQL 5.6 dependencies
+ package: name="mysql-utilities" state=absent
+
+# Configure mysql-apt-config for = "20" or mysql_version == "8.0"
+
+- name: Specify MySQL tool platform to use (Ubuntu20)
+ shell: echo mysql-apt-config mysql-apt-config/unsupported-platform select ubuntu bionic | sudo debconf-set-selections
+ when: ansible_distribution_major_version >= "20" and mysql_version == "5.7"
+
+- name: Specify MySQL tool repo to use (Ubuntu20)
+ shell: echo mysql-apt-config mysql-apt-config/repo-codename select bionic | sudo debconf-set-selections
+ when: ansible_distribution_major_version >= "20" and mysql_version == "5.7"
+
+- name: Specify MySQL tool version to use (Ubuntu20)
+ shell: "echo mysql-apt-config mysql-apt-config/select-server select mysql-{{ mysql_version }} | sudo debconf-set-selections"
+ when: ansible_distribution_major_version >= "20" and mysql_version == "5.7"
+
+- name: Install MySQL apt repo
+ shell: DEBIAN_FRONTEND=noninteractive dpkg --install /tmp/mysql-apt-config_all.deb
+ when: not (ansible_distribution_major_version >= "20" and mysql_version == "8.0")
+
+- name: Add MySQL repo key
+ shell: apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B7B3B788A8D3785C
+ when: ansible_distribution_major_version == "18"
+
+- name: Update apt cache to pull in MySQL repo contents
+ apt: update_cache=yes
+
+- name: Run aptitude safe-upgrade
+ apt: upgrade=safe
+ when: not noupgrade
+
+- name: Install pip
+ package:
+ name: python-pip
+ state: latest
+ update_cache: yes
+ when: ansible_distribution_major_version < "20"
+
+- name: Install MySQL 5.7 client
+ package:
+ name: "{{ 'mysql-client' if mysql_version == '8.0' else 'mysql-community-client' }}"
+ state: present
+
+- name: Install MySQL dependencies
+ package:
+ name: "{{ item }}"
+ state: present
+ with_items:
+ - python3-pymysql
+ - python3-mysqldb
diff --git a/roles/common/tasks/repos-RedHat-7.yml b/roles/common/tasks/repos-RedHat-7.yml
new file mode 100644
index 0000000..e8d43b8
--- /dev/null
+++ b/roles/common/tasks/repos-RedHat-7.yml
@@ -0,0 +1,42 @@
+---
+- name: "Copy over rpm for EPEL Repository for RedHat 7 variants"
+ copy:
+ src: "epel-release-latest-7.noarch.rpm"
+ dest: "/tmp/epel-release-latest-7.noarch.rpm"
+
+- name: "Install rpm for EPEL Repository for RedHat 7 variants"
+ yum:
+ name: "/tmp/epel-release-latest-7.noarch.rpm"
+ state: present
+
+- name: Remove previous MySQL yum repo
+ yum:
+ name: mysql-community-release-el7-5.noarch
+ state: absent
+
+- name: "Set MySQL repo name for {{ mysql_version }}"
+ set_fact:
+ mysql_repo_version: "{{ (mysql_version == '5.7') | ternary('7-11', '7-1') }}"
+
+- name: Fetch MySQL yum repo
+ get_url:
+ url: "https://repo.mysql.com/mysql{{ mysql_version | regex_replace('\\.', '') }}-community-release-el{{ mysql_repo_version }}.noarch.rpm"
+ dest: "/tmp/mysql{{ mysql_version | regex_replace('\\.', '') }}-community-release-el{{ mysql_repo_version }}.noarch.rpm"
+ mode: 0750
+
+- name: Install MySQL repo
+ yum:
+ name: "/tmp/mysql{{ mysql_version | regex_replace('\\.', '') }}-community-release-el{{ mysql_repo_version }}.noarch.rpm"
+ state: present
+
+- name: Update MySQL repo list to use 2022 key
+ shell: rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022
+
+- name: Update MySQL repo list to use 2023 key
+ shell: rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
+
+- name: Install pip
+ package:
+ name: python-pip
+ state: latest
+ update_cache: yes
diff --git a/roles/common/tasks/repos-RedHat-8.yml b/roles/common/tasks/repos-RedHat-8.yml
new file mode 100644
index 0000000..63648a1
--- /dev/null
+++ b/roles/common/tasks/repos-RedHat-8.yml
@@ -0,0 +1,30 @@
+---
+- name: "Copy over rpm for EPEL Repository for RedHat 8 variants"
+ copy:
+ src: "epel-release-latest-8.noarch.rpm"
+ dest: "/tmp/epel-release-latest-8.noarch.rpm"
+
+- name: Install GPG key
+ rpm_key:
+ key: https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-{{ ansible_facts.distribution_major_version }}
+ state: present
+
+- name: Remove previous MySQL yum repo
+ yum:
+ name: mysql-community-release-el7-5.noarch
+ state: absent
+
+- name: Disable default MySQL repo (if using RHEL 8)
+ shell: sudo dnf remove -y @mysql && sudo dnf module reset -y mysql && sudo dnf module disable -y mysql
+ when: mysql_version == "5.7"
+
+- name: Add MySQL repository (if using RHEL 8)
+ copy:
+ src: mysql-community.repo
+ dest: /etc/yum.repos.d/mysql-community.repo
+ when: mysql_version == "5.7"
+
+- name: Disable mysql80 and enable mysql{{ mysql_version | regex_replace('\.', '') }}
+ # Can't use Ansible's built-in yum module because it doesn't support config-manager
+ shell: yum config-manager --enable mysql{{ mysql_version | regex_replace('\.', '') }}-community
+ when: mysql_version == "5.7"
diff --git a/roles/common/tasks/repos-RedHat.yml b/roles/common/tasks/repos-RedHat.yml
new file mode 100644
index 0000000..faadea1
--- /dev/null
+++ b/roles/common/tasks/repos-RedHat.yml
@@ -0,0 +1,7 @@
+---
+- name: Run version-specific repository setup tasks
+ include_tasks: "repos-{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml"
+
+- name: Run yum update
+ yum: name=* state=latest
+ when: not noupgrade
\ No newline at end of file
diff --git a/roles/common/templates/90-apt-proxy.conf.j2 b/roles/common/templates/90-apt-proxy.conf.j2
new file mode 100644
index 0000000..41bf501
--- /dev/null
+++ b/roles/common/templates/90-apt-proxy.conf.j2
@@ -0,0 +1,2 @@
+Acquire::http::Proxy "http://{{ local_apt_cache_host }}:3142";
+Acquire::http::Proxy { download.oracle.com DIRECT; };
diff --git a/roles/common/templates/mime.types b/roles/common/templates/mime.types
new file mode 100644
index 0000000..8d0db47
--- /dev/null
+++ b/roles/common/templates/mime.types
@@ -0,0 +1,1589 @@
+# This file maps Internet media types to unique file extension(s).
+# Although created for httpd, this file is used by many software systems
+# and has been placed in the public domain for unlimited redisribution.
+#
+# The table below contains both registered and (common) unregistered types.
+# A type that has no unique extension can be ignored -- they are listed
+# here to guide configurations toward known types and to make it easier to
+# identify "new" types. File extensions are also commonly used to indicate
+# content languages and encodings, so choose them carefully.
+#
+# Internet media types should be registered as described in RFC 4288.
+# The registry is at .
+#
+# MIME type (lowercased) Extensions
+# ============================================ ==========
+# application/1d-interleaved-parityfec
+# application/3gpp-ims+xml
+# application/activemessage
+application/andrew-inset ez
+# application/applefile
+application/applixware aw
+application/atom+xml atom
+application/atomcat+xml atomcat
+# application/atomicmail
+application/atomsvc+xml atomsvc
+# application/auth-policy+xml
+# application/batch-smtp
+# application/beep+xml
+# application/calendar+xml
+# application/cals-1840
+# application/ccmp+xml
+application/ccxml+xml ccxml
+application/cdmi-capability cdmia
+application/cdmi-container cdmic
+application/cdmi-domain cdmid
+application/cdmi-object cdmio
+application/cdmi-queue cdmiq
+# application/cea-2018+xml
+# application/cellml+xml
+# application/cfw
+# application/cnrp+xml
+# application/commonground
+# application/conference-info+xml
+# application/cpl+xml
+# application/csta+xml
+# application/cstadata+xml
+application/cu-seeme cu
+# application/cybercash
+application/davmount+xml davmount
+# application/dca-rft
+# application/dec-dx
+# application/dialog-info+xml
+# application/dicom
+# application/dns
+application/docbook+xml dbk
+# application/dskpp+xml
+application/dssc+der dssc
+application/dssc+xml xdssc
+# application/dvcs
+application/ecmascript ecma
+# application/edi-consent
+# application/edi-x12
+# application/edifact
+application/emma+xml emma
+# application/epp+xml
+application/epub+zip epub
+# application/eshop
+# application/example
+application/exi exi
+# application/fastinfoset
+# application/fastsoap
+# application/fits
+application/font-tdpfr pfr
+# application/framework-attributes+xml
+application/gml+xml gml
+application/gpx+xml gpx
+application/gxf gxf
+# application/h224
+# application/held+xml
+# application/http
+application/hyperstudio stk
+# application/ibe-key-request+xml
+# application/ibe-pkg-reply+xml
+# application/ibe-pp-data
+# application/iges
+# application/im-iscomposing+xml
+# application/index
+# application/index.cmd
+# application/index.obj
+# application/index.response
+# application/index.vnd
+application/inkml+xml ink inkml
+# application/iotp
+application/ipfix ipfix
+# application/ipp
+# application/isup
+application/java-archive jar
+application/java-serialized-object ser
+application/java-vm class
+application/javascript js
+application/json json
+application/jsonml+json jsonml
+# application/kpml-request+xml
+# application/kpml-response+xml
+application/lost+xml lostxml
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+# application/macwriteii
+application/mads+xml mads
+application/marc mrc
+application/marcxml+xml mrcx
+application/mathematica ma nb mb
+# application/mathml-content+xml
+# application/mathml-presentation+xml
+application/mathml+xml mathml
+# application/mbms-associated-procedure-description+xml
+# application/mbms-deregister+xml
+# application/mbms-envelope+xml
+# application/mbms-msk+xml
+# application/mbms-msk-response+xml
+# application/mbms-protection-description+xml
+# application/mbms-reception-report+xml
+# application/mbms-register+xml
+# application/mbms-register-response+xml
+# application/mbms-user-service-description+xml
+application/mbox mbox
+# application/media_control+xml
+application/mediaservercontrol+xml mscml
+application/metalink+xml metalink
+application/metalink4+xml meta4
+application/mets+xml mets
+# application/mikey
+application/mods+xml mods
+# application/moss-keys
+# application/moss-signature
+# application/mosskey-data
+# application/mosskey-request
+application/mp21 m21 mp21
+application/mp4 mp4s
+# application/mpeg4-generic
+# application/mpeg4-iod
+# application/mpeg4-iod-xmt
+# application/msc-ivr+xml
+# application/msc-mixer+xml
+application/msword doc dot
+application/mxf mxf
+# application/nasdata
+# application/news-checkgroups
+# application/news-groupinfo
+# application/news-transmission
+# application/nss
+# application/ocsp-request
+# application/ocsp-response
+application/octet-stream bin dms lrf mar so dist distz pkg bpk dump elc deploy
+application/oda oda
+application/oebps-package+xml opf
+application/ogg ogx
+application/omdoc+xml omdoc
+application/onenote onetoc onetoc2 onetmp onepkg
+application/oxps oxps
+# application/parityfec
+application/patch-ops-error+xml xer
+application/pdf pdf
+application/pgp-encrypted pgp
+# application/pgp-keys
+application/pgp-signature asc sig
+application/pics-rules prf
+# application/pidf+xml
+# application/pidf-diff+xml
+application/pkcs10 p10
+application/pkcs7-mime p7m p7c
+application/pkcs7-signature p7s
+application/pkcs8 p8
+application/pkix-attr-cert ac
+application/pkix-cert cer
+application/pkix-crl crl
+application/pkix-pkipath pkipath
+application/pkixcmp pki
+application/pls+xml pls
+# application/poc-settings+xml
+application/postscript ai eps ps
+# application/prs.alvestrand.titrax-sheet
+application/prs.cww cww
+# application/prs.nprend
+# application/prs.plucker
+# application/prs.rdf-xml-crypt
+# application/prs.xsf+xml
+application/pskc+xml pskcxml
+# application/qsig
+application/rdf+xml rdf
+application/reginfo+xml rif
+application/relax-ng-compact-syntax rnc
+# application/remote-printing
+application/resource-lists+xml rl
+application/resource-lists-diff+xml rld
+# application/riscos
+# application/rlmi+xml
+application/rls-services+xml rs
+application/rpki-ghostbusters gbr
+application/rpki-manifest mft
+application/rpki-roa roa
+# application/rpki-updown
+application/rsd+xml rsd
+application/rss+xml rss
+application/rtf rtf
+# application/rtx
+# application/samlassertion+xml
+# application/samlmetadata+xml
+application/sbml+xml sbml
+application/scvp-cv-request scq
+application/scvp-cv-response scs
+application/scvp-vp-request spq
+application/scvp-vp-response spp
+application/sdp sdp
+# application/set-payment
+application/set-payment-initiation setpay
+# application/set-registration
+application/set-registration-initiation setreg
+# application/sgml
+# application/sgml-open-catalog
+application/shf+xml shf
+# application/sieve
+# application/simple-filter+xml
+# application/simple-message-summary
+# application/simplesymbolcontainer
+# application/slate
+# application/smil
+application/smil+xml smi smil
+# application/soap+fastinfoset
+# application/soap+xml
+application/sparql-query rq
+application/sparql-results+xml srx
+# application/spirits-event+xml
+application/srgs gram
+application/srgs+xml grxml
+application/sru+xml sru
+application/ssdl+xml ssdl
+application/ssml+xml ssml
+# application/tamp-apex-update
+# application/tamp-apex-update-confirm
+# application/tamp-community-update
+# application/tamp-community-update-confirm
+# application/tamp-error
+# application/tamp-sequence-adjust
+# application/tamp-sequence-adjust-confirm
+# application/tamp-status-query
+# application/tamp-status-response
+# application/tamp-update
+# application/tamp-update-confirm
+application/tei+xml tei teicorpus
+application/thraud+xml tfi
+# application/timestamp-query
+# application/timestamp-reply
+application/timestamped-data tsd
+# application/tve-trigger
+# application/ulpfec
+# application/vcard+xml
+# application/vemmi
+# application/vividence.scriptfile
+# application/vnd.3gpp.bsf+xml
+application/vnd.3gpp.pic-bw-large plb
+application/vnd.3gpp.pic-bw-small psb
+application/vnd.3gpp.pic-bw-var pvb
+# application/vnd.3gpp.sms
+# application/vnd.3gpp2.bcmcsinfo+xml
+# application/vnd.3gpp2.sms
+application/vnd.3gpp2.tcap tcap
+application/vnd.3m.post-it-notes pwn
+application/vnd.accpac.simply.aso aso
+application/vnd.accpac.simply.imp imp
+application/vnd.acucobol acu
+application/vnd.acucorp atc acutc
+application/vnd.adobe.air-application-installer-package+zip air
+application/vnd.adobe.formscentral.fcdt fcdt
+application/vnd.adobe.fxp fxp fxpl
+# application/vnd.adobe.partial-upload
+application/vnd.adobe.xdp+xml xdp
+application/vnd.adobe.xfdf xfdf
+# application/vnd.aether.imp
+# application/vnd.ah-barcode
+application/vnd.ahead.space ahead
+application/vnd.airzip.filesecure.azf azf
+application/vnd.airzip.filesecure.azs azs
+application/vnd.amazon.ebook azw
+application/vnd.americandynamics.acc acc
+application/vnd.amiga.ami ami
+# application/vnd.amundsen.maze+xml
+application/vnd.android.package-archive apk
+application/vnd.anser-web-certificate-issue-initiation cii
+application/vnd.anser-web-funds-transfer-initiation fti
+application/vnd.antix.game-component atx
+application/vnd.apple.installer+xml mpkg
+application/vnd.apple.mpegurl m3u8
+# application/vnd.arastra.swi
+application/vnd.aristanetworks.swi swi
+application/vnd.astraea-software.iota iota
+application/vnd.audiograph aep
+# application/vnd.autopackage
+# application/vnd.avistar+xml
+application/vnd.blueice.multipass mpm
+# application/vnd.bluetooth.ep.oob
+application/vnd.bmi bmi
+application/vnd.businessobjects rep
+# application/vnd.cab-jscript
+# application/vnd.canon-cpdl
+# application/vnd.canon-lips
+# application/vnd.cendio.thinlinc.clientconf
+application/vnd.chemdraw+xml cdxml
+application/vnd.chipnuts.karaoke-mmd mmd
+application/vnd.cinderella cdy
+# application/vnd.cirpack.isdn-ext
+application/vnd.claymore cla
+application/vnd.cloanto.rp9 rp9
+application/vnd.clonk.c4group c4g c4d c4f c4p c4u
+application/vnd.cluetrust.cartomobile-config c11amc
+application/vnd.cluetrust.cartomobile-config-pkg c11amz
+# application/vnd.collection+json
+# application/vnd.commerce-battelle
+application/vnd.commonspace csp
+application/vnd.contact.cmsg cdbcmsg
+application/vnd.cosmocaller cmc
+application/vnd.crick.clicker clkx
+application/vnd.crick.clicker.keyboard clkk
+application/vnd.crick.clicker.palette clkp
+application/vnd.crick.clicker.template clkt
+application/vnd.crick.clicker.wordbank clkw
+application/vnd.criticaltools.wbs+xml wbs
+application/vnd.ctc-posml pml
+# application/vnd.ctct.ws+xml
+# application/vnd.cups-pdf
+# application/vnd.cups-postscript
+application/vnd.cups-ppd ppd
+# application/vnd.cups-raster
+# application/vnd.cups-raw
+# application/vnd.curl
+application/vnd.curl.car car
+application/vnd.curl.pcurl pcurl
+# application/vnd.cybank
+application/vnd.dart dart
+application/vnd.data-vision.rdz rdz
+application/vnd.dece.data uvf uvvf uvd uvvd
+application/vnd.dece.ttml+xml uvt uvvt
+application/vnd.dece.unspecified uvx uvvx
+application/vnd.dece.zip uvz uvvz
+application/vnd.denovo.fcselayout-link fe_launch
+# application/vnd.dir-bi.plate-dl-nosuffix
+application/vnd.dna dna
+application/vnd.dolby.mlp mlp
+# application/vnd.dolby.mobile.1
+# application/vnd.dolby.mobile.2
+application/vnd.dpgraph dpg
+application/vnd.dreamfactory dfac
+application/vnd.ds-keypoint kpxx
+application/vnd.dvb.ait ait
+# application/vnd.dvb.dvbj
+# application/vnd.dvb.esgcontainer
+# application/vnd.dvb.ipdcdftnotifaccess
+# application/vnd.dvb.ipdcesgaccess
+# application/vnd.dvb.ipdcesgaccess2
+# application/vnd.dvb.ipdcesgpdd
+# application/vnd.dvb.ipdcroaming
+# application/vnd.dvb.iptv.alfec-base
+# application/vnd.dvb.iptv.alfec-enhancement
+# application/vnd.dvb.notif-aggregate-root+xml
+# application/vnd.dvb.notif-container+xml
+# application/vnd.dvb.notif-generic+xml
+# application/vnd.dvb.notif-ia-msglist+xml
+# application/vnd.dvb.notif-ia-registration-request+xml
+# application/vnd.dvb.notif-ia-registration-response+xml
+# application/vnd.dvb.notif-init+xml
+# application/vnd.dvb.pfr
+application/vnd.dvb.service svc
+# application/vnd.dxr
+application/vnd.dynageo geo
+# application/vnd.easykaraoke.cdgdownload
+# application/vnd.ecdis-update
+application/vnd.ecowin.chart mag
+# application/vnd.ecowin.filerequest
+# application/vnd.ecowin.fileupdate
+# application/vnd.ecowin.series
+# application/vnd.ecowin.seriesrequest
+# application/vnd.ecowin.seriesupdate
+# application/vnd.emclient.accessrequest+xml
+application/vnd.enliven nml
+# application/vnd.eprints.data+xml
+application/vnd.epson.esf esf
+application/vnd.epson.msf msf
+application/vnd.epson.quickanime qam
+application/vnd.epson.salt slt
+application/vnd.epson.ssf ssf
+# application/vnd.ericsson.quickcall
+application/vnd.eszigno3+xml es3 et3
+# application/vnd.etsi.aoc+xml
+# application/vnd.etsi.cug+xml
+# application/vnd.etsi.iptvcommand+xml
+# application/vnd.etsi.iptvdiscovery+xml
+# application/vnd.etsi.iptvprofile+xml
+# application/vnd.etsi.iptvsad-bc+xml
+# application/vnd.etsi.iptvsad-cod+xml
+# application/vnd.etsi.iptvsad-npvr+xml
+# application/vnd.etsi.iptvservice+xml
+# application/vnd.etsi.iptvsync+xml
+# application/vnd.etsi.iptvueprofile+xml
+# application/vnd.etsi.mcid+xml
+# application/vnd.etsi.overload-control-policy-dataset+xml
+# application/vnd.etsi.sci+xml
+# application/vnd.etsi.simservs+xml
+# application/vnd.etsi.tsl+xml
+# application/vnd.etsi.tsl.der
+# application/vnd.eudora.data
+application/vnd.ezpix-album ez2
+application/vnd.ezpix-package ez3
+# application/vnd.f-secure.mobile
+application/vnd.fdf fdf
+application/vnd.fdsn.mseed mseed
+application/vnd.fdsn.seed seed dataless
+# application/vnd.ffsns
+# application/vnd.fints
+application/vnd.flographit gph
+application/vnd.fluxtime.clip ftc
+# application/vnd.font-fontforge-sfd
+application/vnd.framemaker fm frame maker book
+application/vnd.frogans.fnc fnc
+application/vnd.frogans.ltf ltf
+application/vnd.fsc.weblaunch fsc
+application/vnd.fujitsu.oasys oas
+application/vnd.fujitsu.oasys2 oa2
+application/vnd.fujitsu.oasys3 oa3
+application/vnd.fujitsu.oasysgp fg5
+application/vnd.fujitsu.oasysprs bh2
+# application/vnd.fujixerox.art-ex
+# application/vnd.fujixerox.art4
+# application/vnd.fujixerox.hbpl
+application/vnd.fujixerox.ddd ddd
+application/vnd.fujixerox.docuworks xdw
+application/vnd.fujixerox.docuworks.binder xbd
+# application/vnd.fut-misnet
+application/vnd.fuzzysheet fzs
+application/vnd.genomatix.tuxedo txd
+# application/vnd.geocube+xml
+application/vnd.geogebra.file ggb
+application/vnd.geogebra.tool ggt
+application/vnd.geometry-explorer gex gre
+application/vnd.geonext gxt
+application/vnd.geoplan g2w
+application/vnd.geospace g3w
+# application/vnd.globalplatform.card-content-mgt
+# application/vnd.globalplatform.card-content-mgt-response
+application/vnd.gmx gmx
+application/vnd.google-earth.kml+xml kml
+application/vnd.google-earth.kmz kmz
+application/vnd.grafeq gqf gqs
+# application/vnd.gridmp
+application/vnd.groove-account gac
+application/vnd.groove-help ghf
+application/vnd.groove-identity-message gim
+application/vnd.groove-injector grv
+application/vnd.groove-tool-message gtm
+application/vnd.groove-tool-template tpl
+application/vnd.groove-vcard vcg
+# application/vnd.hal+json
+application/vnd.hal+xml hal
+application/vnd.handheld-entertainment+xml zmm
+application/vnd.hbci hbci
+# application/vnd.hcl-bireports
+application/vnd.hhe.lesson-player les
+application/vnd.hp-hpgl hpgl
+application/vnd.hp-hpid hpid
+application/vnd.hp-hps hps
+application/vnd.hp-jlyt jlt
+application/vnd.hp-pcl pcl
+application/vnd.hp-pclxl pclxl
+# application/vnd.httphone
+application/vnd.hydrostatix.sof-data sfd-hdstx
+# application/vnd.hzn-3d-crossword
+# application/vnd.ibm.afplinedata
+# application/vnd.ibm.electronic-media
+application/vnd.ibm.minipay mpy
+application/vnd.ibm.modcap afp listafp list3820
+application/vnd.ibm.rights-management irm
+application/vnd.ibm.secure-container sc
+application/vnd.iccprofile icc icm
+application/vnd.igloader igl
+application/vnd.immervision-ivp ivp
+application/vnd.immervision-ivu ivu
+# application/vnd.informedcontrol.rms+xml
+# application/vnd.informix-visionary
+# application/vnd.infotech.project
+# application/vnd.infotech.project+xml
+# application/vnd.innopath.wamp.notification
+application/vnd.insors.igm igm
+application/vnd.intercon.formnet xpw xpx
+application/vnd.intergeo i2g
+# application/vnd.intertrust.digibox
+# application/vnd.intertrust.nncp
+application/vnd.intu.qbo qbo
+application/vnd.intu.qfx qfx
+# application/vnd.iptc.g2.conceptitem+xml
+# application/vnd.iptc.g2.knowledgeitem+xml
+# application/vnd.iptc.g2.newsitem+xml
+# application/vnd.iptc.g2.newsmessage+xml
+# application/vnd.iptc.g2.packageitem+xml
+# application/vnd.iptc.g2.planningitem+xml
+application/vnd.ipunplugged.rcprofile rcprofile
+application/vnd.irepository.package+xml irp
+application/vnd.is-xpr xpr
+application/vnd.isac.fcs fcs
+application/vnd.jam jam
+# application/vnd.japannet-directory-service
+# application/vnd.japannet-jpnstore-wakeup
+# application/vnd.japannet-payment-wakeup
+# application/vnd.japannet-registration
+# application/vnd.japannet-registration-wakeup
+# application/vnd.japannet-setstore-wakeup
+# application/vnd.japannet-verification
+# application/vnd.japannet-verification-wakeup
+application/vnd.jcp.javame.midlet-rms rms
+application/vnd.jisp jisp
+application/vnd.joost.joda-archive joda
+application/vnd.kahootz ktz ktr
+application/vnd.kde.karbon karbon
+application/vnd.kde.kchart chrt
+application/vnd.kde.kformula kfo
+application/vnd.kde.kivio flw
+application/vnd.kde.kontour kon
+application/vnd.kde.kpresenter kpr kpt
+application/vnd.kde.kspread ksp
+application/vnd.kde.kword kwd kwt
+application/vnd.kenameaapp htke
+application/vnd.kidspiration kia
+application/vnd.kinar kne knp
+application/vnd.koan skp skd skt skm
+application/vnd.kodak-descriptor sse
+application/vnd.las.las+xml lasxml
+# application/vnd.liberty-request+xml
+application/vnd.llamagraphics.life-balance.desktop lbd
+application/vnd.llamagraphics.life-balance.exchange+xml lbe
+application/vnd.lotus-1-2-3 123
+application/vnd.lotus-approach apr
+application/vnd.lotus-freelance pre
+application/vnd.lotus-notes nsf
+application/vnd.lotus-organizer org
+application/vnd.lotus-screencam scm
+application/vnd.lotus-wordpro lwp
+application/vnd.macports.portpkg portpkg
+# application/vnd.marlin.drm.actiontoken+xml
+# application/vnd.marlin.drm.conftoken+xml
+# application/vnd.marlin.drm.license+xml
+# application/vnd.marlin.drm.mdcf
+application/vnd.mcd mcd
+application/vnd.medcalcdata mc1
+application/vnd.mediastation.cdkey cdkey
+# application/vnd.meridian-slingshot
+application/vnd.mfer mwf
+application/vnd.mfmp mfm
+application/vnd.micrografx.flo flo
+application/vnd.micrografx.igx igx
+application/vnd.mif mif
+# application/vnd.minisoft-hp3000-save
+# application/vnd.mitsubishi.misty-guard.trustweb
+application/vnd.mobius.daf daf
+application/vnd.mobius.dis dis
+application/vnd.mobius.mbk mbk
+application/vnd.mobius.mqy mqy
+application/vnd.mobius.msl msl
+application/vnd.mobius.plc plc
+application/vnd.mobius.txf txf
+application/vnd.mophun.application mpn
+application/vnd.mophun.certificate mpc
+# application/vnd.motorola.flexsuite
+# application/vnd.motorola.flexsuite.adsi
+# application/vnd.motorola.flexsuite.fis
+# application/vnd.motorola.flexsuite.gotap
+# application/vnd.motorola.flexsuite.kmr
+# application/vnd.motorola.flexsuite.ttc
+# application/vnd.motorola.flexsuite.wem
+# application/vnd.motorola.iprm
+application/vnd.mozilla.xul+xml xul
+application/vnd.ms-artgalry cil
+# application/vnd.ms-asf
+application/vnd.ms-cab-compressed cab
+# application/vnd.ms-color.iccprofile
+application/vnd.ms-excel xls xlm xla xlc xlt xlw
+application/vnd.ms-excel.addin.macroenabled.12 xlam
+application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb
+application/vnd.ms-excel.sheet.macroenabled.12 xlsm
+application/vnd.ms-excel.template.macroenabled.12 xltm
+application/vnd.ms-fontobject eot
+application/vnd.ms-htmlhelp chm
+application/vnd.ms-ims ims
+application/vnd.ms-lrm lrm
+# application/vnd.ms-office.activex+xml
+application/vnd.ms-officetheme thmx
+# application/vnd.ms-opentype
+# application/vnd.ms-package.obfuscated-opentype
+application/vnd.ms-pki.seccat cat
+application/vnd.ms-pki.stl stl
+# application/vnd.ms-playready.initiator+xml
+application/vnd.ms-powerpoint ppt pps pot
+application/vnd.ms-powerpoint.addin.macroenabled.12 ppam
+application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm
+application/vnd.ms-powerpoint.slide.macroenabled.12 sldm
+application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm
+application/vnd.ms-powerpoint.template.macroenabled.12 potm
+# application/vnd.ms-printing.printticket+xml
+application/vnd.ms-project mpp mpt
+# application/vnd.ms-tnef
+# application/vnd.ms-wmdrm.lic-chlg-req
+# application/vnd.ms-wmdrm.lic-resp
+# application/vnd.ms-wmdrm.meter-chlg-req
+# application/vnd.ms-wmdrm.meter-resp
+application/vnd.ms-word.document.macroenabled.12 docm
+application/vnd.ms-word.template.macroenabled.12 dotm
+application/vnd.ms-works wps wks wcm wdb
+application/vnd.ms-wpl wpl
+application/vnd.ms-xpsdocument xps
+application/vnd.mseq mseq
+# application/vnd.msign
+# application/vnd.multiad.creator
+# application/vnd.multiad.creator.cif
+# application/vnd.music-niff
+application/vnd.musician mus
+application/vnd.muvee.style msty
+application/vnd.mynfc taglet
+# application/vnd.ncd.control
+# application/vnd.ncd.reference
+# application/vnd.nervana
+# application/vnd.netfpx
+application/vnd.neurolanguage.nlu nlu
+application/vnd.nitf ntf nitf
+application/vnd.noblenet-directory nnd
+application/vnd.noblenet-sealer nns
+application/vnd.noblenet-web nnw
+# application/vnd.nokia.catalogs
+# application/vnd.nokia.conml+wbxml
+# application/vnd.nokia.conml+xml
+# application/vnd.nokia.isds-radio-presets
+# application/vnd.nokia.iptv.config+xml
+# application/vnd.nokia.landmark+wbxml
+# application/vnd.nokia.landmark+xml
+# application/vnd.nokia.landmarkcollection+xml
+# application/vnd.nokia.n-gage.ac+xml
+application/vnd.nokia.n-gage.data ngdat
+application/vnd.nokia.n-gage.symbian.install n-gage
+# application/vnd.nokia.ncd
+# application/vnd.nokia.pcd+wbxml
+# application/vnd.nokia.pcd+xml
+application/vnd.nokia.radio-preset rpst
+application/vnd.nokia.radio-presets rpss
+application/vnd.novadigm.edm edm
+application/vnd.novadigm.edx edx
+application/vnd.novadigm.ext ext
+# application/vnd.ntt-local.file-transfer
+# application/vnd.ntt-local.sip-ta_remote
+# application/vnd.ntt-local.sip-ta_tcp_stream
+application/vnd.oasis.opendocument.chart odc
+application/vnd.oasis.opendocument.chart-template otc
+application/vnd.oasis.opendocument.database odb
+application/vnd.oasis.opendocument.formula odf
+application/vnd.oasis.opendocument.formula-template odft
+application/vnd.oasis.opendocument.graphics odg
+application/vnd.oasis.opendocument.graphics-template otg
+application/vnd.oasis.opendocument.image odi
+application/vnd.oasis.opendocument.image-template oti
+application/vnd.oasis.opendocument.presentation odp
+application/vnd.oasis.opendocument.presentation-template otp
+application/vnd.oasis.opendocument.spreadsheet ods
+application/vnd.oasis.opendocument.spreadsheet-template ots
+application/vnd.oasis.opendocument.text odt
+application/vnd.oasis.opendocument.text-master odm
+application/vnd.oasis.opendocument.text-template ott
+application/vnd.oasis.opendocument.text-web oth
+# application/vnd.obn
+# application/vnd.oftn.l10n+json
+# application/vnd.oipf.contentaccessdownload+xml
+# application/vnd.oipf.contentaccessstreaming+xml
+# application/vnd.oipf.cspg-hexbinary
+# application/vnd.oipf.dae.svg+xml
+# application/vnd.oipf.dae.xhtml+xml
+# application/vnd.oipf.mippvcontrolmessage+xml
+# application/vnd.oipf.pae.gem
+# application/vnd.oipf.spdiscovery+xml
+# application/vnd.oipf.spdlist+xml
+# application/vnd.oipf.ueprofile+xml
+# application/vnd.oipf.userprofile+xml
+application/vnd.olpc-sugar xo
+# application/vnd.oma-scws-config
+# application/vnd.oma-scws-http-request
+# application/vnd.oma-scws-http-response
+# application/vnd.oma.bcast.associated-procedure-parameter+xml
+# application/vnd.oma.bcast.drm-trigger+xml
+# application/vnd.oma.bcast.imd+xml
+# application/vnd.oma.bcast.ltkm
+# application/vnd.oma.bcast.notification+xml
+# application/vnd.oma.bcast.provisioningtrigger
+# application/vnd.oma.bcast.sgboot
+# application/vnd.oma.bcast.sgdd+xml
+# application/vnd.oma.bcast.sgdu
+# application/vnd.oma.bcast.simple-symbol-container
+# application/vnd.oma.bcast.smartcard-trigger+xml
+# application/vnd.oma.bcast.sprov+xml
+# application/vnd.oma.bcast.stkm
+# application/vnd.oma.cab-address-book+xml
+# application/vnd.oma.cab-feature-handler+xml
+# application/vnd.oma.cab-pcc+xml
+# application/vnd.oma.cab-user-prefs+xml
+# application/vnd.oma.dcd
+# application/vnd.oma.dcdc
+application/vnd.oma.dd2+xml dd2
+# application/vnd.oma.drm.risd+xml
+# application/vnd.oma.group-usage-list+xml
+# application/vnd.oma.pal+xml
+# application/vnd.oma.poc.detailed-progress-report+xml
+# application/vnd.oma.poc.final-report+xml
+# application/vnd.oma.poc.groups+xml
+# application/vnd.oma.poc.invocation-descriptor+xml
+# application/vnd.oma.poc.optimized-progress-report+xml
+# application/vnd.oma.push
+# application/vnd.oma.scidm.messages+xml
+# application/vnd.oma.xcap-directory+xml
+# application/vnd.omads-email+xml
+# application/vnd.omads-file+xml
+# application/vnd.omads-folder+xml
+# application/vnd.omaloc-supl-init
+application/vnd.openofficeorg.extension oxt
+# application/vnd.openxmlformats-officedocument.custom-properties+xml
+# application/vnd.openxmlformats-officedocument.customxmlproperties+xml
+# application/vnd.openxmlformats-officedocument.drawing+xml
+# application/vnd.openxmlformats-officedocument.drawingml.chart+xml
+# application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml
+# application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml
+# application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml
+# application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml
+# application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml
+# application/vnd.openxmlformats-officedocument.extended-properties+xml
+# application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml
+# application/vnd.openxmlformats-officedocument.presentationml.comments+xml
+# application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml
+# application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml
+# application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml
+application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
+# application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml
+# application/vnd.openxmlformats-officedocument.presentationml.presprops+xml
+application/vnd.openxmlformats-officedocument.presentationml.slide sldx
+# application/vnd.openxmlformats-officedocument.presentationml.slide+xml
+# application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml
+# application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml
+application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx
+# application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml
+# application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml
+# application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml
+# application/vnd.openxmlformats-officedocument.presentationml.tags+xml
+application/vnd.openxmlformats-officedocument.presentationml.template potx
+# application/vnd.openxmlformats-officedocument.presentationml.template.main+xml
+# application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml
+application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
+# application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml
+application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx
+# application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml
+# application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml
+# application/vnd.openxmlformats-officedocument.theme+xml
+# application/vnd.openxmlformats-officedocument.themeoverride+xml
+# application/vnd.openxmlformats-officedocument.vmldrawing
+# application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml
+application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
+# application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml
+application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
+# application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml
+# application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml
+# application/vnd.openxmlformats-package.core-properties+xml
+# application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml
+# application/vnd.openxmlformats-package.relationships+xml
+# application/vnd.quobject-quoxdocument
+# application/vnd.osa.netdeploy
+application/vnd.osgeo.mapguide.package mgp
+# application/vnd.osgi.bundle
+application/vnd.osgi.dp dp
+application/vnd.osgi.subsystem esa
+# application/vnd.otps.ct-kip+xml
+application/vnd.palm pdb pqa oprc
+# application/vnd.paos.xml
+application/vnd.pawaafile paw
+application/vnd.pg.format str
+application/vnd.pg.osasli ei6
+# application/vnd.piaccess.application-licence
+application/vnd.picsel efif
+application/vnd.pmi.widget wg
+# application/vnd.poc.group-advertisement+xml
+application/vnd.pocketlearn plf
+application/vnd.powerbuilder6 pbd
+# application/vnd.powerbuilder6-s
+# application/vnd.powerbuilder7
+# application/vnd.powerbuilder7-s
+# application/vnd.powerbuilder75
+# application/vnd.powerbuilder75-s
+# application/vnd.preminet
+application/vnd.previewsystems.box box
+application/vnd.proteus.magazine mgz
+application/vnd.publishare-delta-tree qps
+application/vnd.pvi.ptid1 ptid
+# application/vnd.pwg-multiplexed
+# application/vnd.pwg-xhtml-print+xml
+# application/vnd.qualcomm.brew-app-res
+application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb
+# application/vnd.radisys.moml+xml
+# application/vnd.radisys.msml+xml
+# application/vnd.radisys.msml-audit+xml
+# application/vnd.radisys.msml-audit-conf+xml
+# application/vnd.radisys.msml-audit-conn+xml
+# application/vnd.radisys.msml-audit-dialog+xml
+# application/vnd.radisys.msml-audit-stream+xml
+# application/vnd.radisys.msml-conf+xml
+# application/vnd.radisys.msml-dialog+xml
+# application/vnd.radisys.msml-dialog-base+xml
+# application/vnd.radisys.msml-dialog-fax-detect+xml
+# application/vnd.radisys.msml-dialog-fax-sendrecv+xml
+# application/vnd.radisys.msml-dialog-group+xml
+# application/vnd.radisys.msml-dialog-speech+xml
+# application/vnd.radisys.msml-dialog-transform+xml
+# application/vnd.rainstor.data
+# application/vnd.rapid
+application/vnd.realvnc.bed bed
+application/vnd.recordare.musicxml mxl
+application/vnd.recordare.musicxml+xml musicxml
+# application/vnd.renlearn.rlprint
+application/vnd.rig.cryptonote cryptonote
+application/vnd.rim.cod cod
+application/vnd.rn-realmedia rm
+application/vnd.rn-realmedia-vbr rmvb
+application/vnd.route66.link66+xml link66
+# application/vnd.rs-274x
+# application/vnd.ruckus.download
+# application/vnd.s3sms
+application/vnd.sailingtracker.track st
+# application/vnd.sbm.cid
+# application/vnd.sbm.mid2
+# application/vnd.scribus
+# application/vnd.sealed.3df
+# application/vnd.sealed.csf
+# application/vnd.sealed.doc
+# application/vnd.sealed.eml
+# application/vnd.sealed.mht
+# application/vnd.sealed.net
+# application/vnd.sealed.ppt
+# application/vnd.sealed.tiff
+# application/vnd.sealed.xls
+# application/vnd.sealedmedia.softseal.html
+# application/vnd.sealedmedia.softseal.pdf
+application/vnd.seemail see
+application/vnd.sema sema
+application/vnd.semd semd
+application/vnd.semf semf
+application/vnd.shana.informed.formdata ifm
+application/vnd.shana.informed.formtemplate itp
+application/vnd.shana.informed.interchange iif
+application/vnd.shana.informed.package ipk
+application/vnd.simtech-mindmapper twd twds
+application/vnd.smaf mmf
+# application/vnd.smart.notebook
+application/vnd.smart.teacher teacher
+# application/vnd.software602.filler.form+xml
+# application/vnd.software602.filler.form-xml-zip
+application/vnd.solent.sdkm+xml sdkm sdkd
+application/vnd.spotfire.dxp dxp
+application/vnd.spotfire.sfs sfs
+# application/vnd.sss-cod
+# application/vnd.sss-dtf
+# application/vnd.sss-ntf
+application/vnd.stardivision.calc sdc
+application/vnd.stardivision.draw sda
+application/vnd.stardivision.impress sdd
+application/vnd.stardivision.math smf
+application/vnd.stardivision.writer sdw vor
+application/vnd.stardivision.writer-global sgl
+application/vnd.stepmania.package smzip
+application/vnd.stepmania.stepchart sm
+# application/vnd.street-stream
+application/vnd.sun.xml.calc sxc
+application/vnd.sun.xml.calc.template stc
+application/vnd.sun.xml.draw sxd
+application/vnd.sun.xml.draw.template std
+application/vnd.sun.xml.impress sxi
+application/vnd.sun.xml.impress.template sti
+application/vnd.sun.xml.math sxm
+application/vnd.sun.xml.writer sxw
+application/vnd.sun.xml.writer.global sxg
+application/vnd.sun.xml.writer.template stw
+# application/vnd.sun.wadl+xml
+application/vnd.sus-calendar sus susp
+application/vnd.svd svd
+# application/vnd.swiftview-ics
+application/vnd.symbian.install sis sisx
+application/vnd.syncml+xml xsm
+application/vnd.syncml.dm+wbxml bdm
+application/vnd.syncml.dm+xml xdm
+# application/vnd.syncml.dm.notification
+# application/vnd.syncml.ds.notification
+application/vnd.tao.intent-module-archive tao
+application/vnd.tcpdump.pcap pcap cap dmp
+application/vnd.tmobile-livetv tmo
+application/vnd.trid.tpt tpt
+application/vnd.triscape.mxs mxs
+application/vnd.trueapp tra
+# application/vnd.truedoc
+# application/vnd.ubisoft.webplayer
+application/vnd.ufdl ufd ufdl
+application/vnd.uiq.theme utz
+application/vnd.umajin umj
+application/vnd.unity unityweb
+application/vnd.uoml+xml uoml
+# application/vnd.uplanet.alert
+# application/vnd.uplanet.alert-wbxml
+# application/vnd.uplanet.bearer-choice
+# application/vnd.uplanet.bearer-choice-wbxml
+# application/vnd.uplanet.cacheop
+# application/vnd.uplanet.cacheop-wbxml
+# application/vnd.uplanet.channel
+# application/vnd.uplanet.channel-wbxml
+# application/vnd.uplanet.list
+# application/vnd.uplanet.list-wbxml
+# application/vnd.uplanet.listcmd
+# application/vnd.uplanet.listcmd-wbxml
+# application/vnd.uplanet.signal
+application/vnd.vcx vcx
+# application/vnd.vd-study
+# application/vnd.vectorworks
+# application/vnd.verimatrix.vcas
+# application/vnd.vidsoft.vidconference
+application/vnd.visio vsd vst vss vsw
+application/vnd.visionary vis
+# application/vnd.vividence.scriptfile
+application/vnd.vsf vsf
+# application/vnd.wap.sic
+# application/vnd.wap.slc
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/vnd.webturbo wtb
+# application/vnd.wfa.wsc
+# application/vnd.wmc
+# application/vnd.wmf.bootstrap
+# application/vnd.wolfram.mathematica
+# application/vnd.wolfram.mathematica.package
+application/vnd.wolfram.player nbp
+application/vnd.wordperfect wpd
+application/vnd.wqd wqd
+# application/vnd.wrq-hp3000-labelled
+application/vnd.wt.stf stf
+# application/vnd.wv.csp+wbxml
+# application/vnd.wv.csp+xml
+# application/vnd.wv.ssp+xml
+application/vnd.xara xar
+application/vnd.xfdl xfdl
+# application/vnd.xfdl.webform
+# application/vnd.xmi+xml
+# application/vnd.xmpie.cpkg
+# application/vnd.xmpie.dpkg
+# application/vnd.xmpie.plan
+# application/vnd.xmpie.ppkg
+# application/vnd.xmpie.xlim
+application/vnd.yamaha.hv-dic hvd
+application/vnd.yamaha.hv-script hvs
+application/vnd.yamaha.hv-voice hvp
+application/vnd.yamaha.openscoreformat osf
+application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg
+# application/vnd.yamaha.remote-setup
+application/vnd.yamaha.smaf-audio saf
+application/vnd.yamaha.smaf-phrase spf
+# application/vnd.yamaha.through-ngn
+# application/vnd.yamaha.tunnel-udpencap
+application/vnd.yellowriver-custom-menu cmp
+application/vnd.zul zir zirz
+application/vnd.zzazz.deck+xml zaz
+application/voicexml+xml vxml
+# application/vq-rtcpxr
+# application/watcherinfo+xml
+# application/whoispp-query
+# application/whoispp-response
+application/widget wgt
+application/winhlp hlp
+# application/wita
+# application/wordperfect5.1
+application/wsdl+xml wsdl
+application/wspolicy+xml wspolicy
+application/x-7z-compressed 7z
+application/x-abiword abw
+application/x-ace-compressed ace
+# application/x-amf
+application/x-apple-diskimage dmg
+application/x-authorware-bin aab x32 u32 vox
+application/x-authorware-map aam
+application/x-authorware-seg aas
+application/x-bcpio bcpio
+application/x-bittorrent torrent
+application/x-blorb blb blorb
+application/x-bzip bz
+application/x-bzip2 bz2 boz
+application/x-cbr cbr cba cbt cbz cb7
+application/x-cdlink vcd
+application/x-cfs-compressed cfs
+application/x-chat chat
+application/x-chess-pgn pgn
+application/x-conference nsc
+# application/x-compress
+application/x-cpio cpio
+application/x-csh csh
+application/x-debian-package deb udeb
+application/x-dgc-compressed dgc
+application/x-director dir dcr dxr cst cct cxt w3d fgd swa
+application/x-doom wad
+application/x-dtbncx+xml ncx
+application/x-dtbook+xml dtb
+application/x-dtbresource+xml res
+application/x-dvi dvi
+application/x-envoy evy
+application/x-eva eva
+application/x-font-bdf bdf
+# application/x-font-dos
+# application/x-font-framemaker
+application/x-font-ghostscript gsf
+# application/x-font-libgrx
+application/x-font-linux-psf psf
+application/x-font-otf otf
+application/x-font-pcf pcf
+application/x-font-snf snf
+# application/x-font-speedo
+# application/x-font-sunos-news
+application/x-font-ttf ttf ttc
+application/x-font-type1 pfa pfb pfm afm
+application/x-font-woff woff
+# application/x-font-vfont
+application/x-freearc arc
+application/x-futuresplash spl
+application/x-gca-compressed gca
+application/x-glulx ulx
+application/x-gnumeric gnumeric
+application/x-gramps-xml gramps
+application/x-gtar gtar
+# application/x-gzip
+application/x-hdf hdf
+application/x-install-instructions install
+application/x-iso9660-image iso
+application/x-java-jnlp-file jnlp
+application/x-latex latex
+application/x-lzh-compressed lzh lha
+application/x-mie mie
+application/x-mobipocket-ebook prc mobi
+application/x-ms-application application
+application/x-ms-shortcut lnk
+application/x-ms-wmd wmd
+application/x-ms-wmz wmz
+application/x-ms-xbap xbap
+application/x-msaccess mdb
+application/x-msbinder obd
+application/x-mscardfile crd
+application/x-msclip clp
+application/x-msdownload exe dll com bat msi
+application/x-msmediaview mvb m13 m14
+application/x-msmetafile wmf wmz emf emz
+application/x-msmoney mny
+application/x-mspublisher pub
+application/x-msschedule scd
+application/x-msterminal trm
+application/x-mswrite wri
+application/x-netcdf nc cdf
+application/x-nzb nzb
+application/x-pkcs12 p12 pfx
+application/x-pkcs7-certificates p7b spc
+application/x-pkcs7-certreqresp p7r
+application/x-rar-compressed rar
+application/x-research-info-systems ris
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-silverlight-app xap
+application/x-sql sql
+application/x-stuffit sit
+application/x-stuffitx sitx
+application/x-subrip srt
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-t3vm-image t3
+application/x-tads gam
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-tex-tfm tfm
+application/x-texinfo texinfo texi
+application/x-tgif obj
+application/x-ustar ustar
+application/x-wais-source src
+application/x-x509-ca-cert der crt
+application/x-xfig fig
+application/x-xliff+xml xlf
+application/x-xpinstall xpi
+application/x-xz xz
+application/x-zmachine z1 z2 z3 z4 z5 z6 z7 z8
+# application/x400-bp
+application/xaml+xml xaml
+# application/xcap-att+xml
+# application/xcap-caps+xml
+application/xcap-diff+xml xdf
+# application/xcap-el+xml
+# application/xcap-error+xml
+# application/xcap-ns+xml
+# application/xcon-conference-info-diff+xml
+# application/xcon-conference-info+xml
+application/xenc+xml xenc
+application/xhtml+xml xhtml xht
+# application/xhtml-voice+xml
+application/xml xml xsl
+application/xml-dtd dtd
+# application/xml-external-parsed-entity
+# application/xmpp+xml
+application/xop+xml xop
+application/xproc+xml xpl
+application/xslt+xml xslt
+application/xspf+xml xspf
+application/xv+xml mxml xhvml xvml xvm
+application/yang yang
+application/yin+xml yin
+application/zip zip
+# audio/1d-interleaved-parityfec
+# audio/32kadpcm
+# audio/3gpp
+# audio/3gpp2
+# audio/ac3
+audio/adpcm adp
+# audio/amr
+# audio/amr-wb
+# audio/amr-wb+
+# audio/asc
+# audio/atrac-advanced-lossless
+# audio/atrac-x
+# audio/atrac3
+audio/basic au snd
+# audio/bv16
+# audio/bv32
+# audio/clearmode
+# audio/cn
+# audio/dat12
+# audio/dls
+# audio/dsr-es201108
+# audio/dsr-es202050
+# audio/dsr-es202211
+# audio/dsr-es202212
+# audio/dv
+# audio/dvi4
+# audio/eac3
+# audio/evrc
+# audio/evrc-qcp
+# audio/evrc0
+# audio/evrc1
+# audio/evrcb
+# audio/evrcb0
+# audio/evrcb1
+# audio/evrcwb
+# audio/evrcwb0
+# audio/evrcwb1
+# audio/example
+# audio/fwdred
+# audio/g719
+# audio/g722
+# audio/g7221
+# audio/g723
+# audio/g726-16
+# audio/g726-24
+# audio/g726-32
+# audio/g726-40
+# audio/g728
+# audio/g729
+# audio/g7291
+# audio/g729d
+# audio/g729e
+# audio/gsm
+# audio/gsm-efr
+# audio/gsm-hr-08
+# audio/ilbc
+# audio/ip-mr_v2.5
+# audio/isac
+# audio/l16
+# audio/l20
+# audio/l24
+# audio/l8
+# audio/lpc
+audio/midi mid midi kar rmi
+# audio/mobile-xmf
+audio/mp4 mp4a
+# audio/mp4a-latm
+# audio/mpa
+# audio/mpa-robust
+audio/mpeg mpga mp2 mp2a mp3 m2a m3a
+# audio/mpeg4-generic
+# audio/musepack
+audio/ogg oga ogg spx
+# audio/opus
+# audio/parityfec
+# audio/pcma
+# audio/pcma-wb
+# audio/pcmu-wb
+# audio/pcmu
+# audio/prs.sid
+# audio/qcelp
+# audio/red
+# audio/rtp-enc-aescm128
+# audio/rtp-midi
+# audio/rtx
+audio/s3m s3m
+audio/silk sil
+# audio/smv
+# audio/smv0
+# audio/smv-qcp
+# audio/sp-midi
+# audio/speex
+# audio/t140c
+# audio/t38
+# audio/telephone-event
+# audio/tone
+# audio/uemclip
+# audio/ulpfec
+# audio/vdvi
+# audio/vmr-wb
+# audio/vnd.3gpp.iufp
+# audio/vnd.4sb
+# audio/vnd.audiokoz
+# audio/vnd.celp
+# audio/vnd.cisco.nse
+# audio/vnd.cmles.radio-events
+# audio/vnd.cns.anp1
+# audio/vnd.cns.inf1
+audio/vnd.dece.audio uva uvva
+audio/vnd.digital-winds eol
+# audio/vnd.dlna.adts
+# audio/vnd.dolby.heaac.1
+# audio/vnd.dolby.heaac.2
+# audio/vnd.dolby.mlp
+# audio/vnd.dolby.mps
+# audio/vnd.dolby.pl2
+# audio/vnd.dolby.pl2x
+# audio/vnd.dolby.pl2z
+# audio/vnd.dolby.pulse.1
+audio/vnd.dra dra
+audio/vnd.dts dts
+audio/vnd.dts.hd dtshd
+# audio/vnd.dvb.file
+# audio/vnd.everad.plj
+# audio/vnd.hns.audio
+audio/vnd.lucent.voice lvp
+audio/vnd.ms-playready.media.pya pya
+# audio/vnd.nokia.mobile-xmf
+# audio/vnd.nortel.vbk
+audio/vnd.nuera.ecelp4800 ecelp4800
+audio/vnd.nuera.ecelp7470 ecelp7470
+audio/vnd.nuera.ecelp9600 ecelp9600
+# audio/vnd.octel.sbc
+# audio/vnd.qcelp
+# audio/vnd.rhetorex.32kadpcm
+audio/vnd.rip rip
+# audio/vnd.sealedmedia.softseal.mpeg
+# audio/vnd.vmx.cvsd
+# audio/vorbis
+# audio/vorbis-config
+audio/webm weba
+audio/x-aac aac
+audio/x-aiff aif aiff aifc
+audio/x-caf caf
+audio/x-flac flac
+audio/x-matroska mka
+audio/x-mpegurl m3u
+audio/x-ms-wax wax
+audio/x-ms-wma wma
+audio/x-pn-realaudio ram ra
+audio/x-pn-realaudio-plugin rmp
+# audio/x-tta
+audio/x-wav wav
+audio/xm xm
+chemical/x-cdx cdx
+chemical/x-cif cif
+chemical/x-cmdf cmdf
+chemical/x-cml cml
+chemical/x-csml csml
+# chemical/x-pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm cgm
+# image/example
+# image/fits
+image/g3fax g3
+image/gif gif
+image/ief ief
+# image/jp2
+image/jpeg jpeg jpg jpe
+# image/jpm
+# image/jpx
+image/ktx ktx
+# image/naplps
+image/png png
+image/prs.btif btif
+# image/prs.pti
+image/sgi sgi
+image/svg+xml svg svgz
+# image/t38
+image/tiff tiff tif
+# image/tiff-fx
+image/vnd.adobe.photoshop psd
+# image/vnd.cns.inf2
+image/vnd.dece.graphic uvi uvvi uvg uvvg
+image/vnd.dvb.subtitle sub
+image/vnd.djvu djvu djv
+image/vnd.dwg dwg
+image/vnd.dxf dxf
+image/vnd.fastbidsheet fbs
+image/vnd.fpx fpx
+image/vnd.fst fst
+image/vnd.fujixerox.edmics-mmr mmr
+image/vnd.fujixerox.edmics-rlc rlc
+# image/vnd.globalgraphics.pgb
+# image/vnd.microsoft.icon
+# image/vnd.mix
+image/vnd.ms-modi mdi
+image/vnd.ms-photo wdp
+image/vnd.net-fpx npx
+# image/vnd.radiance
+# image/vnd.sealed.png
+# image/vnd.sealedmedia.softseal.gif
+# image/vnd.sealedmedia.softseal.jpg
+# image/vnd.svf
+image/vnd.wap.wbmp wbmp
+image/vnd.xiff xif
+image/webp webp
+image/x-3ds 3ds
+image/x-cmu-raster ras
+image/x-cmx cmx
+image/x-freehand fh fhc fh4 fh5 fh7
+image/x-icon ico
+image/x-mrsid-image sid
+image/x-pcx pcx
+image/x-pict pic pct
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-rgb rgb
+image/x-tga tga
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+# message/cpim
+# message/delivery-status
+# message/disposition-notification
+# message/example
+# message/external-body
+# message/feedback-report
+# message/global
+# message/global-delivery-status
+# message/global-disposition-notification
+# message/global-headers
+# message/http
+# message/imdn+xml
+# message/news
+# message/partial
+message/rfc822 eml mime
+# message/s-http
+# message/sip
+# message/sipfrag
+# message/tracking-status
+# message/vnd.si.simp
+# model/example
+model/iges igs iges
+model/mesh msh mesh silo
+model/vnd.collada+xml dae
+model/vnd.dwf dwf
+# model/vnd.flatland.3dml
+model/vnd.gdl gdl
+# model/vnd.gs-gdl
+# model/vnd.gs.gdl
+model/vnd.gtw gtw
+# model/vnd.moml+xml
+model/vnd.mts mts
+# model/vnd.parasolid.transmit.binary
+# model/vnd.parasolid.transmit.text
+model/vnd.vtu vtu
+model/vrml wrl vrml
+model/x3d+binary x3db x3dbz
+model/x3d+vrml x3dv x3dvz
+model/x3d+xml x3d x3dz
+# multipart/alternative
+# multipart/appledouble
+# multipart/byteranges
+# multipart/digest
+# multipart/encrypted
+# multipart/example
+# multipart/form-data
+# multipart/header-set
+# multipart/mixed
+# multipart/parallel
+# multipart/related
+# multipart/report
+# multipart/signed
+# multipart/voice-message
+# text/1d-interleaved-parityfec
+text/cache-manifest appcache manifest
+text/calendar ics ifb
+text/css css
+text/csv csv
+# text/directory
+# text/dns
+# text/ecmascript
+# text/enriched
+# text/example
+# text/fwdred
+text/html html htm
+# text/javascript
+text/n3 n3
+# text/parityfec
+text/plain txt text conf def list log in
+# text/prs.fallenstein.rst
+text/prs.lines.tag dsc
+# text/vnd.radisys.msml-basic-layout
+# text/red
+# text/rfc822-headers
+text/richtext rtx
+# text/rtf
+# text/rtp-enc-aescm128
+# text/rtx
+text/sgml sgml sgm
+# text/t140
+text/tab-separated-values tsv
+text/troff t tr roff man me ms
+text/turtle ttl
+# text/ulpfec
+text/uri-list uri uris urls
+text/vcard vcard
+# text/vnd.abc
+text/vnd.curl curl
+text/vnd.curl.dcurl dcurl
+text/vnd.curl.scurl scurl
+text/vnd.curl.mcurl mcurl
+# text/vnd.dmclientscript
+text/vnd.dvb.subtitle sub
+# text/vnd.esmertec.theme-descriptor
+text/vnd.fly fly
+text/vnd.fmi.flexstor flx
+text/vnd.graphviz gv
+text/vnd.in3d.3dml 3dml
+text/vnd.in3d.spot spot
+# text/vnd.iptc.newsml
+# text/vnd.iptc.nitf
+# text/vnd.latex-z
+# text/vnd.motorola.reflex
+# text/vnd.ms-mediapackage
+# text/vnd.net2phone.commcenter.command
+# text/vnd.si.uricatalogue
+text/vnd.sun.j2me.app-descriptor jad
+# text/vnd.trolltech.linguist
+# text/vnd.wap.si
+# text/vnd.wap.sl
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-asm s asm
+text/x-c c cc cxx cpp h hh dic
+text/x-fortran f for f77 f90
+text/x-java-source java
+text/x-opml opml
+text/x-pascal p pas
+text/x-nfo nfo
+text/x-setext etx
+text/x-sfv sfv
+text/x-uuencode uu
+text/x-vcalendar vcs
+text/x-vcard vcf
+# text/xml
+# text/xml-external-parsed-entity
+# video/1d-interleaved-parityfec
+video/3gpp 3gp
+# video/3gpp-tt
+video/3gpp2 3g2
+# video/bmpeg
+# video/bt656
+# video/celb
+# video/dv
+# video/example
+video/h261 h261
+video/h263 h263
+# video/h263-1998
+# video/h263-2000
+video/h264 h264
+# video/h264-rcdo
+# video/h264-svc
+video/jpeg jpgv
+# video/jpeg2000
+video/jpm jpm jpgm
+video/mj2 mj2 mjp2
+# video/mp1s
+# video/mp2p
+# video/mp2t
+video/mp4 mp4 mp4v mpg4
+# video/mp4v-es
+video/mpeg mpeg mpg mpe m1v m2v
+# video/mpeg4-generic
+# video/mpv
+# video/nv
+video/ogg ogv
+# video/parityfec
+# video/pointer
+video/quicktime qt mov
+# video/raw
+# video/rtp-enc-aescm128
+# video/rtx
+# video/smpte292m
+# video/ulpfec
+# video/vc1
+# video/vnd.cctv
+video/vnd.dece.hd uvh uvvh
+video/vnd.dece.mobile uvm uvvm
+# video/vnd.dece.mp4
+video/vnd.dece.pd uvp uvvp
+video/vnd.dece.sd uvs uvvs
+video/vnd.dece.video uvv uvvv
+# video/vnd.directv.mpeg
+# video/vnd.directv.mpeg-tts
+# video/vnd.dlna.mpeg-tts
+video/vnd.dvb.file dvb
+video/vnd.fvt fvt
+# video/vnd.hns.video
+# video/vnd.iptvforum.1dparityfec-1010
+# video/vnd.iptvforum.1dparityfec-2005
+# video/vnd.iptvforum.2dparityfec-1010
+# video/vnd.iptvforum.2dparityfec-2005
+# video/vnd.iptvforum.ttsavc
+# video/vnd.iptvforum.ttsmpeg2
+# video/vnd.motorola.video
+# video/vnd.motorola.videop
+video/vnd.mpegurl mxu m4u
+video/vnd.ms-playready.media.pyv pyv
+# video/vnd.nokia.interleaved-multimedia
+# video/vnd.nokia.videovoip
+# video/vnd.objectvideo
+# video/vnd.sealed.mpeg1
+# video/vnd.sealed.mpeg4
+# video/vnd.sealed.swf
+# video/vnd.sealedmedia.softseal.mov
+video/vnd.uvvu.mp4 uvu uvvu
+video/vnd.vivo viv
+video/webm webm
+video/x-f4v f4v
+video/x-fli fli
+video/x-flv flv
+video/x-m4v m4v
+video/x-matroska mkv mk3d mks
+video/x-mng mng
+video/x-ms-asf asf asx
+video/x-ms-vob vob
+video/x-ms-wm wm
+video/x-ms-wmv wmv
+video/x-ms-wmx wmx
+video/x-ms-wvx wvx
+video/x-msvideo avi
+video/x-sgi-movie movie
+video/x-smv smv
+x-conference/x-cooltalk ice
+
diff --git a/roles/common/vars/Debian.yml b/roles/common/vars/Debian.yml
new file mode 100644
index 0000000..08449c8
--- /dev/null
+++ b/roles/common/vars/Debian.yml
@@ -0,0 +1,4 @@
+---
+common_platform_packages:
+ - vim-nox
+ - openssl
diff --git a/roles/common/vars/RedHat.yml b/roles/common/vars/RedHat.yml
new file mode 100644
index 0000000..419cec8
--- /dev/null
+++ b/roles/common/vars/RedHat.yml
@@ -0,0 +1,8 @@
+---
+common_platform_packages:
+ - emacs-nox
+ - vim-enhanced
+ - openssl
+ - openssl-devel
+ - make # Used to build zlib, when necessary
+ - redhat-rpm-config # Used to compile mod_jk, used by Apache
diff --git a/roles/common/vars/main.yml b/roles/common/vars/main.yml
new file mode 100644
index 0000000..ad25750
--- /dev/null
+++ b/roles/common/vars/main.yml
@@ -0,0 +1,16 @@
+---
+common_packages:
+ - curl
+ - git
+ - unzip
+ - kpartx
+ - gdisk
+ - libtool
+ - logwatch
+ - postfix
+ - xfsprogs
+ - zip
+ - wget
+ - nano
+ - zip
+ - unzip
\ No newline at end of file
diff --git a/roles/content-controller/defaults/main.yml b/roles/content-controller/defaults/main.yml
new file mode 100644
index 0000000..762069c
--- /dev/null
+++ b/roles/content-controller/defaults/main.yml
@@ -0,0 +1,130 @@
+---
+cc_db_host: localhost
+cc_db_username: ccontroller
+cc_db_name: contentcontroller
+cc_db_url: "jdbc:mysql:{{ cc_db_failover_parameter }}//{{ cc_db_host }}:3306/{{ cc_db_name }}"
+
+#In order to download releases of Content Controller, you'll need to have a keypair
+#(The folks at Rustici will provide you with a keypair to use)
+s3_download_key:
+s3_download_secret:
+use_s3_release: false
+
+# This stuff is only here for when we're doing internal builds.
+# The download won't work outside of our internal network, anyway.
+# But if you want to break a bunch of stuff, be my guest :-)
+use_art: false
+art_url: http://artifactory.rusticisoftware.com/artifactory/cc-snapshot-local/zips/contentcontroller-snapshot.zip
+use_minio: false
+
+# This should match the name of the build that you have in roles/content-controller/files (if deploying locally)
+build_name: ContentController-4.0.655
+
+cc_version: "{{ (build_name | regex_replace('ContentController-', '')) }}"
+is_3p3_or_later: "{{ cc_version is version('3.3', '>=') }}"
+is_4p0_or_later: "{{ cc_version is version('4.0', '>=') }}"
+enable_aws_connection: "{{ S3FileStorageEnabled or video_transcode_threshold is defined }}"
+
+# These settings provision the initial user for the Content Controller web interface.
+cc_username: 'test.user@example.com'
+cc_user_full_name: 'Test User'
+cc_user_password: 'test^passWord12'
+cc_user_email: 'test.user@example.com'
+
+# Run DB migrations (these should only be run from one box during a multi-box deploy)
+cc_run_db_migrations: true
+
+# Escalate rights for ContentController database user when running migrations
+escalate: true
+
+# Specify a failover parameter. Options are failover:, sequential:, replication:, aurora:
+# https://mariadb.com/kb/en/library/failover-and-high-availability-with-mariadb-connector-j/
+# It should include the end colon, please
+cc_db_failover_parameter: ""
+
+# Set the query used to validate a DB connection. A good reason for changing this would be to check if a connection
+# is read only. Example:
+# INSERT INTO `object_store` (object_key_sha1, object_key)
+# VALUES (unhex('ad522e07a0ee37459059aa16eb6a45d000827a32'), 'contentcontroller/healthcheck')
+# ON DUPLICATE KEY UPDATE update_dt = now();
+cc_db_connection_initialization_query: "/* ping */ -- Content Controller Health Check"
+
+# Define connection pool sizes (these can usually be left at their default values)
+cc_db_pool_min_size: 8
+cc_db_pool_max_size: 32
+cc_db_pool_initial_size: 10 # Initial size must be greater than, or equal to `minSize`
+
+# Send metrics data to Datadog.
+use_datadog: false
+
+# prevent user data from being exposed in the UI.
+# Note that this is a string value, not a boolean, as fills in a string in the contentcontroller.yml template file.
+sanitizeLearnerInfo: "false"
+
+# The The maximum idle time for a connection, once established. Append "ms" to the value
+jerseyClientTimeout: 5000ms
+# The maximum time to wait for a connection to open.
+jerseyClientConnectionTimeout: 2000ms
+
+# The number of background threads to use for import jobs.
+import_threads: 10
+
+# Enable remote JVM debugging for the contentcontroller-service application.
+remote_debugging: false
+
+# Defined, but null by default
+ssl_chain:
+
+# The expiration time for auth tokens in minutes. The minimum value is 1
+# minute, the maximum value is 43200 minutes (30 days), and the default
+# is 1440 minutes (1 day).
+token_exp: 1440
+
+generated_file_location: "{{ data_root }}/reports"
+generated_file_webpath: /reports
+
+generated_course_zip_location: "{{ content_root }}/zips"
+generated_course_zip_webpath: /zips
+
+daysToKeepExportedVersionZips: 90
+
+# Public Automation API (disabled by default)
+enable_automation_api: false
+
+# Enable emails (disabled by default)
+enable_emails: false
+
+# S3 Defaults
+cc_s3_http_port: 80
+cc_s3_https_port: 443
+cc_s3_https_only: true
+cc_s3_disable_dns_buckets: false
+
+# NOTE: If you think you need a local content proxy, please contact
+# Rustici Software support for more information. We'll need to grant
+# you access to the release bucket.
+content_proxy_enabled: false
+
+# Logging defaults
+cc_custom_log_levels: {}
+
+media_convert_service_role: "{{ S3MediaConvertServiceRole | default('') }}"
+
+enable_queue_polling: true
+# poll the queue every 10 seconds
+queue_polling_interval: 10
+# Integration with Rustici's Webhook Testing Service (disabled by default)
+enable_testing_service: false
+
+enable_contentvault: false
+contentvault_expiration_seconds: 0
+contentvault_sliding_window_seconds: 0
+contentvault_window_cache_duration: 0
+contentvault_fields_to_validate: ""
+
+# Populate initial_cc_users for legacy support
+initial_cc_users:
+ - username: "{{ cc_username }}"
+ full_name: "{{ cc_user_full_name }}"
+ password: "{{ cc_user_password }}"
+ email: "{{ cc_user_email }}"
\ No newline at end of file
diff --git a/roles/content-controller/handlers/main.yml b/roles/content-controller/handlers/main.yml
new file mode 100644
index 0000000..4082ef5
--- /dev/null
+++ b/roles/content-controller/handlers/main.yml
@@ -0,0 +1,18 @@
+---
+ - import_tasks: roles/apache/handlers/main.yml
+
+ - import_tasks: roles/mysql-config/handlers/main.yml
+
+ - name: start contentcontroller
+ action: service name=contentcontroller state=started
+ notify: restart httpd
+
+ - name: restart contentcontroller
+ action: service name=contentcontroller state=restarted daemon_reload=yes
+ notify: restart httpd
+
+ - name: stop contentcontroller
+ action: service name=contentcontroller state=stopped
+
+ - name: stop cchealthcheck
+ action: service name=cchealthcheck state=stopped daemon_reload=yes
diff --git a/roles/content-controller/meta/main.yml b/roles/content-controller/meta/main.yml
new file mode 100644
index 0000000..ffaed26
--- /dev/null
+++ b/roles/content-controller/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - { role: java }
diff --git a/roles/content-controller/tasks/launcher_link_tasks.yml b/roles/content-controller/tasks/launcher_link_tasks.yml
new file mode 100644
index 0000000..d21dd31
--- /dev/null
+++ b/roles/content-controller/tasks/launcher_link_tasks.yml
@@ -0,0 +1,35 @@
+- name: Determine Launch Link release name
+ set_fact:
+ launcher_link_build_name: "{{ build_name | regex_replace('ContentController', 'LauncherLink') }}"
+
+- name: copy over launcherlink archive
+ copy: src="{{ launcher_link_build_name }}.zip" dest="/tmp/{{ launcher_link_build_name }}.zip" mode=0700 force=true
+ when: not use_art and not use_s3_release
+
+
+- name: Fetch Launcher Link from S3
+ aws_s3:
+ bucket="rustici-release-cc-launcherlink"
+ object={{ item.src }}
+ dest={{ item.dest }}
+ mode=get
+ aws_access_key={{ s3_download_key }}
+ aws_secret_key={{ s3_download_secret }}
+ region="us-east-1"
+ when: use_s3_release
+ with_items:
+ - { src: "{{ launcher_link_build_name }}.zip", dest: "/tmp/{{ launcher_link_build_name }}.zip" }
+ - { src: "{{ launcher_link_build_name }}.zip.sha1", dest: "/tmp/{{ launcher_link_build_name }}.zip.sha1" }
+
+
+- name: create path for launcher link files
+ file:
+ path: "{{ html_path }}/launcherlink"
+ state: directory
+
+- name: unzip launcher link archive on target machine
+ unarchive:
+ src="/tmp/{{ launcher_link_build_name }}.zip"
+ dest="{{ html_path }}/launcherlink"
+ copy=no
+ force=yes
\ No newline at end of file
diff --git a/roles/content-controller/tasks/main.yml b/roles/content-controller/tasks/main.yml
new file mode 100644
index 0000000..5294be1
--- /dev/null
+++ b/roles/content-controller/tasks/main.yml
@@ -0,0 +1,304 @@
+---
+- name: clean up staging directory
+ shell: "rm -rf {{ temp_path }}"
+
+- name: Set facts for external tasks
+ set_fact:
+ build_name: "{{ build_name }}"
+
+- name: Fetch the Checksum of the latest release of Content Controller from S3
+ aws_s3:
+ bucket="rustici-release-cc"
+ object={{ item.src }}
+ dest={{ item.dest }}
+ mode=get
+ aws_access_key={{ s3_download_key }}
+ aws_secret_key={{ s3_download_secret }}
+ region="us-east-1"
+ when: not use_art and use_s3_release
+ with_items:
+ - { src: "{{ build_name }}.zip.sha1", dest: "/tmp/{{ build_name }}.zip.sha1" }
+
+- name: Check to see if we already have a copy of the archive in place
+ stat: path="/tmp/{{ build_name }}.zip"
+ register: build
+
+- name: Fetch contents of downloaded checksum to compare to current build checksum
+ command: "cat /tmp/{{ build_name }}.zip.sha1"
+ register: checksum
+ when: not use_art and use_s3_release
+
+- name: check the checksum
+ command: /bin/true
+ when: (not use_art and use_s3_release) and build.stat.exists and (build.stat.checksum == checksum.stdout)
+ register: filesequal
+ ignore_errors: true
+
+- name: copy over contentcontroller archive
+ copy: src="{{ build_name }}.zip" dest="/tmp/{{ build_name }}.zip" mode=0700 force=true
+ when: not use_art and not use_s3_release
+ register: ccupdated
+
+- name: Fetch a fresh build of Content Controller if you're doing something special
+ get_url: url="{{ art_url }}" dest="/tmp/{{ build_name }}.zip" mode=0700
+ when: use_art and not use_s3_release
+ register: ccupdated
+
+- name: Fetch the latest release of Content Controller from S3
+ aws_s3:
+ bucket="rustici-release-cc"
+ object={{ item.src }}
+ dest={{ item.dest }}
+ mode=get
+ aws_access_key={{ s3_download_key }}
+ aws_secret_key={{ s3_download_secret }}
+ region="us-east-1"
+ when: not use_art and use_s3_release and (not filesequal or not build.stat.exists)
+ with_items:
+ - { src: "{{ build_name }}.zip", dest: "/tmp/{{ build_name }}.zip" }
+ - { src: "{{ build_name }}.zip.sha1", dest: "/tmp/{{ build_name }}.zip.sha1" }
+ register: ccupdated
+
+- name: create path for deployment files
+ file:
+ path: "{{ temp_path }}"
+ state: directory
+
+- name: ensure that path for client files already exists
+ file: path="{{ html_path }}" state=directory mode=0755
+
+- name: symlink courses folder into the html_path
+ file: path="{{ html_path }}courses" src="{{ courses_filepath }}" state=link
+
+- name: symlink courses folder into minio data path
+ file:
+ path: "{{ minio_data_path }}/{{ S3FileStorageBucket }}"
+ src: "{{ content_root }}"
+ state: link
+ when: use_minio
+
+- name: unzip archive on target machine
+ unarchive:
+ src="/tmp/{{ build_name }}.zip"
+ dest="{{ temp_path }}"
+ copy=no
+ force=yes
+
+- name: check if ScormEngineInterface war file exists
+ stat:
+ path: "{{ temp_path }}/engine/ScormEngineInterface.war"
+ register: scorm_engine_war
+
+- name: Register ScormEngine war name
+ set_fact:
+ scorm_engine_war_name: true
+ rustici_engine_war_name: false
+ cacheable: yes
+ when: scorm_engine_war.stat.exists == True
+
+- name: Register RusticiEngine war name
+ set_fact:
+ rustici_engine_war_name: true
+ scorm_engine_war_name: false
+ cacheable: yes
+ when: scorm_engine_war.stat.exists == False
+
+- name: check if V2 API client exists
+ stat:
+ path: "{{ temp_path }}/service/lib/RusticiSoftware.Core.api.client.v2.jar"
+ register: v2_client
+
+- name: Register V2 client exists
+ set_fact:
+ engine_use_v2_api: true
+ cacheable: yes
+ when: v2_client.stat.exists == True
+
+- name: Register V2 client does not exist
+ set_fact:
+ engine_use_v2_api: false
+ cacheable: yes
+ when: v2_client.stat.exists == False
+
+- name: Create and set permissions on logfile
+ file:
+ path: "/var/log/{{ item }}"
+ state: touch
+ owner: tomcat
+ group: tomcat
+ mode: 0640
+ with_items:
+ - contentcontroller.log
+ - contentcontroller-audit.log
+
+- name: Creates cc_deploy_path directory
+ file: path={{ cc_deploy_path }} state=directory mode=0750 owner=tomcat group=tomcat
+
+- name: Copy service files to cc_deploy_path
+ shell: "cp -rf {{ temp_path }}/service/. {{ cc_deploy_path }}"
+ notify:
+ - restart contentcontroller
+
+- name: Create config file with DB migration values
+ template:
+ src: contentcontroller.yaml.j2
+ dest: "{{ cc_deploy_path }}/bin/contentcontroller.yaml"
+ mode: 0644
+ vars:
+ db_migration: true
+ when: cc_run_db_migrations
+
+- name: escalate rights for ccontroller mysql user to run migrations
+ mysql_user: name="{{ cc_db_username }}" host="%" login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} password="{{ cc_db_password }}" priv="{{ cc_db_name }}.*:SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,REFERENCES,INDEX,ALTER,CREATE TEMPORARY TABLES,LOCK TABLES,EXECUTE,CREATE VIEW,SHOW VIEW,CREATE ROUTINE,ALTER ROUTINE,TRIGGER" state=present
+ notify: flush privs
+ when: escalate and cc_run_db_migrations
+ run_once: "{{ run_db_tasks_once }}"
+
+- name: run db migration
+ shell: "cd {{ cc_deploy_path }}/bin; ./contentcontroller-service db migrate --outOfOrder contentcontroller.yaml"
+ when: cc_run_db_migrations
+ run_once: "{{ run_db_tasks_once }}"
+
+- name: set mysql rights back to minimum required set post migration
+ mysql_user: name="{{ cc_db_username }}" host="%" login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} password="{{ cc_db_password }}" priv="{{ cc_db_name }}.*:SELECT,INSERT,UPDATE,DELETE,LOCK TABLES,EXECUTE,TRIGGER,CREATE TEMPORARY TABLES" state=present
+ notify: flush privs
+ when: escalate and cc_run_db_migrations
+ run_once: "{{ run_db_tasks_once }}"
+
+- name: Create config file
+ template:
+ src: contentcontroller.yaml.j2
+ dest: "{{ cc_deploy_path }}/bin/contentcontroller.yaml"
+ mode: 0644
+
+- name: install client files
+ shell: "cp -a {{ temp_path }}/www/. {{ html_path }}"
+ notify:
+ - restart contentcontroller
+
+- name: add initial user(s)
+ shell: cd {{ cc_deploy_path }}/bin; ./contentcontroller-service user contentcontroller.yaml create -u "{{ item.username }}" -n "{{ item.full_name }}" -p "{{ item.password }}" -e "{{ item.email }}" -r "ADMIN"
+ when: initialize_mysql
+ loop: "{{ initial_cc_users }}"
+ run_once: "{{ run_db_tasks_once }}"
+ no_log: true
+
+- name: check to see if systemd is enabled
+ shell: "[[ `systemctl` =~ -\\.mount ]] && echo yes || echo no"
+ args:
+ executable: /bin/bash
+ register: cc_systemd_check
+
+- name: check to see if upstart is enabled
+ shell: "[[ `/sbin/init --version` =~ upstart ]] && echo yes || echo no"
+ args:
+ executable: /bin/bash
+ register: cc_upstart_check
+
+- name: set facts for init check
+ set_fact:
+ cc_using_upstart: "{{ cc_upstart_check.stdout | bool }}"
+ cc_using_systemd: "{{ cc_systemd_check.stdout | bool }}"
+
+- name: install upstart script (Ubuntu)
+ template:
+ src=contentcontroller.conf.j2
+ dest=/etc/init/contentcontroller.conf
+ mode=0644
+ notify:
+ - restart contentcontroller
+ when: ansible_os_family == 'Debian' and cc_using_upstart
+
+- name: install systemd init script
+ template:
+ src=contentcontroller.service.j2
+ dest=/etc/systemd/system/contentcontroller.service
+ mode=0644
+ notify:
+ - start contentcontroller
+ when: cc_using_systemd
+
+- name: create SDXD directories
+ file:
+ path: "{{ item }}"
+ state: directory
+ recurse: yes
+ with_items:
+ - '/var/www/html/sdxd/SCORMDriverXD_CS'
+ - '/var/www/html/sdxd/remotecontent'
+
+- name: install SDXD files
+ template: src=templates/{{item.src}} dest=/var/www/html/sdxd/{{item.dest}} mode=0664
+ with_items:
+ - {src: 'configuration.js.j2', dest: 'SCORMDriverXD_CS/configuration.js'}
+ - {src: 'contentAPI.html.j2', dest: 'SCORMDriverXD_CS/contentAPI.html'}
+ - {src: 'contentsample.html.j2', dest: 'remotecontent/contentsample.html'}
+ notify:
+ - restart contentcontroller
+
+- name: archive SDXD files for download
+ shell: "rm -f /var/www/html/sdxd/{{item.dest}} && zip -j /var/www/html/sdxd/{{item.dest}} /var/www/html/sdxd/{{item.src}}"
+ with_items:
+ - {src: 'SCORMDriverXD_CS/contentAPI.html', dest: 'SCORMDriverXD_CS/contentAPI.zip'}
+ - {src: 'remotecontent/contentsample.html', dest: 'remotecontent/contentsample.zip'}
+
+- name: Check for RXD
+ stat:
+ path: "{{ html_path }}rxd/"
+ register: rxd
+
+- name: Set RXD host in static files
+ when: rxd.stat.exists == True
+ replace:
+ path: "{{ item }}"
+ regexp: "\\{\\{ServerName\\}\\}" # Regex matching the literal token
+ replace: "{{ ServerName }}" # Replacing that token with the Ansible variable of the same name
+ with_items:
+ - "{{ html_path }}rxd/contentAPI.html"
+ - "{{ html_path }}rxd/rustici-xd-pkg-api.min.js"
+
+- name: archive RXD files for download
+ shell: "rm -f /var/www/html/rxd/{{item}}.zip && zip -j /var/www/html/rxd/{{item}}.zip /var/www/html/rxd/{{item}}.html"
+ when: rxd.stat.exists == True
+ with_items:
+ - 'contentAPI'
+ - 'sample-course/index'
+
+- name: Move custom favicons if provided
+ copy:
+ src: "{{item.src}}"
+ dest: "/var/www/html/{{item.dest}}"
+ mode: '0644'
+ with_items:
+ - {src: 'favicon-16x16.png', dest: 'favicon-16x16.png'}
+ - {src: 'favicon-32x32.png', dest: 'favicon-32x32.png'}
+ # Most people won't provide custom favicons, so let's avoid printing a scary error when this fails
+ failed_when: false
+
+- name: ensure that service is enabled
+ service: name=contentcontroller enabled=yes state=started
+
+- name: Register files in temp_path/service/lib directory
+ find:
+ paths: "{{ temp_path }}/service/lib"
+ recurse: yes
+ register: temp_files
+
+- name: Register files in cc_deploy_path
+ find:
+ paths: "{{ cc_deploy_path }}/lib"
+ recurse: yes
+ register: deployed_files
+
+- set_fact:
+ temp_file_names: "{{ temp_files.files | map(attribute='path') | map('basename') | list }}"
+ deployed_file_names: "{{ deployed_files.files | map(attribute='path') | map('basename') | list }}"
+
+- name: Delete files in cc_deploy_path that don't exist in temp_files
+ file:
+ path: "{{ cc_deploy_path }}/lib/{{ item }}"
+ state: absent
+ loop: "{{ deployed_file_names | difference(temp_file_names) }}"
+
+- include_tasks: launcher_link_tasks.yml
+ when: use_launcher_link is defined and use_launcher_link
diff --git a/roles/content-controller/templates/configuration.js.j2 b/roles/content-controller/templates/configuration.js.j2
new file mode 100644
index 0000000..2929998
--- /dev/null
+++ b/roles/content-controller/templates/configuration.js.j2
@@ -0,0 +1 @@
+var CC_RESULTS_URL = "https://{{ ServerName }}/api/sdxd/results";
diff --git a/roles/content-controller/templates/contentAPI.html.j2 b/roles/content-controller/templates/contentAPI.html.j2
new file mode 100644
index 0000000..f383428
--- /dev/null
+++ b/roles/content-controller/templates/contentAPI.html.j2
@@ -0,0 +1,15 @@
+
+
+
+
+ Rustici Cross Domain
+
+
+
+
diff --git a/roles/content-controller/templates/contentcontroller.conf.j2 b/roles/content-controller/templates/contentcontroller.conf.j2
new file mode 100644
index 0000000..a2ecd6a
--- /dev/null
+++ b/roles/content-controller/templates/contentcontroller.conf.j2
@@ -0,0 +1,36 @@
+description "Content Controller Server"
+
+ start on runlevel [2345]
+ stop on runlevel [!2345]
+ respawn
+ respawn limit 10 5
+
+ # run as non privileged user
+ # add user with this command:
+ ## adduser --system --ingroup www-data --home /opt/apache-tomcat apache-tomcat
+ # Ubuntu 12.04: (use 'exec sudo -u apache-tomcat' when using 10.04)
+ setuid tomcat
+ setgid tomcat
+
+ # adapt paths - Replace with your Paths:
+ env JAVA_HOME={{ java_home }}
+ env BINDIR={{ cc_deploy_path }}/bin
+
+ # adapt java options to suit your needs:
+ env JAVA_OPTS="-Djava.awt.headless=true -Xmx{{ cc_heap_size }} -XX:+UseConcMarkSweepGC"
+ {% if remote_debugging %}
+ env CONTENTCONTROLLER_SERVICE_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000"
+ {% endif %}
+
+ {% if ClientName is defined %}
+ env NEW_RELIC_APP_NAME="{{ ClientName }}-{{ env }}-ContentController"
+ {% else %}
+ env NEW_RELIC_APP_NAME="{{ env }}"
+ {% endif %}
+
+ exec $BINDIR/./contentcontroller-service server $BINDIR/./contentcontroller.yaml
+
+ # cleanup temp directory after stop
+ post-stop script
+ # This is here as a placeholder
+ end script
diff --git a/roles/content-controller/templates/contentcontroller.service.j2 b/roles/content-controller/templates/contentcontroller.service.j2
new file mode 100644
index 0000000..8c92b55
--- /dev/null
+++ b/roles/content-controller/templates/contentcontroller.service.j2
@@ -0,0 +1,23 @@
+[Unit]
+Description=Content Controller Web Application Container
+After=network.target
+
+[Service]
+Environment=JAVA_HOME={{ java_home }}
+Environment=BINDIR={{ cc_deploy_path }}/bin
+Environment="JAVA_OPTS=-Djava.awt.headless=true -Xmx{{ cc_heap_size }} -XX:+UseConcMarkSweepGC"
+{% if remote_debugging %}
+Environment="CONTENTCONTROLLER_SERVICE_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address={% if is_java_11 %}*:{% endif %}8000"
+{% endif %}
+WorkingDir={{ cc_deploy_path }}/bin
+ExecStart={{ cc_deploy_path }}/bin/./contentcontroller-service server {{ cc_deploy_path }}/bin/contentcontroller.yaml
+User=tomcat
+Group=tomcat
+
+{% if S3FileStorageAwsId is defined and S3FileStorageAwsKey is defined %}
+Environment=AWS_ACCESS_KEY_ID="{{ S3FileStorageAwsId }}"
+Environment=AWS_SECRET_ACCESS_KEY="{{ S3FileStorageAwsKey }}"
+{% endif %}
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/content-controller/templates/contentcontroller.yaml.j2 b/roles/content-controller/templates/contentcontroller.yaml.j2
new file mode 100644
index 0000000..7e4a565
--- /dev/null
+++ b/roles/content-controller/templates/contentcontroller.yaml.j2
@@ -0,0 +1,556 @@
+# The URL at which this application is hosted. This value is used when building dispatch
+# packages for export, so the URL *must* be fully-qualified and accessible from client
+# LMSs. If this value ever changes, then all dispatch packages must be re-exported!
+
+applicationHost: https://{{ ServerName }}
+
+# The URL at which the service layer itself is hosted. Typically, this will be the same
+# as applicationHost. This setting is used when importing content, but is not persisted
+# anywhere - so it may be changed without causing any issues.
+serviceHost: http://{{ serviceHost }}:{{ CC_API_PORT }}
+
+# Jetty server configuration settings.
+server:
+ rootPath: /api/*
+ applicationConnectors:
+ - type: http
+ port: {{ CC_API_PORT }}
+{% if is_4p0_or_later %}
+ useForwardedHeaders: true
+{% endif %}
+ adminConnectors:
+ - type: http
+ port: {{ CC_ADMIN_PORT }}
+
+# Database connection settings. At a minimum, you will need to set the
+# username, password, and JDBC url.
+# NOTE: Content Controller supports MySQL 5.6 and newer.
+database:
+ # The username and password for connecting to the database.
+ user: {{ cc_db_username }}
+ password: {{ cc_db_password }}
+ # The JDBC URL (e.g, jdbc:mysql://localhost:3306/contentcontroller).
+ url: {{ cc_db_url }}
+ properties:
+ useSsl: {{ db_use_ssl }}
+ enabledSslProtocolSuites: {{ db_ssl_protocols }}
+ {% if db_migration is defined and db_migration %}
+ # Increase the timeouts if we're running the DB migration to ensure
+ # that we leave time for schema changes to happen.
+ socketTimeout: {{ 9999999 }}
+ connectTimeout: {{ 9999999 }}
+ {% endif %}
+
+{% if cc_db_use_utf8 is defined and cc_db_use_utf8|bool %}
+ charSet: UTF-8
+ characterEncoding: UTF-8
+ useUnicode: true
+{% endif %}
+
+ # If you prefer to use the MySQL Connector/J library, place the JAR file in
+ # the lib folder, and change this setting to "com.mysql.jdbc.Driver".
+ driverClass: org.mariadb.jdbc.Driver
+
+ # The remaining settings can usually be left at their default values.
+ # See http://www.dropwizard.io/0.9.1/docs/manual/configuration.html#database
+ # for available settings.
+ maxWaitForConnection: 5s
+ initializationQuery: "{{ cc_db_connection_initialization_query }}"
+ validationQuery: "/* ping */ -- Content Controller Health Check"
+ validationQueryTimeout: 3s
+ minSize: {{ cc_db_pool_min_size }}
+ maxSize: {{ cc_db_pool_max_size }}
+ initialSize: {{ cc_db_pool_initial_size }} # Initial size must be greater than, or equal to `minSize`
+ checkConnectionWhileIdle: true
+ checkConnectionOnConnect: true
+ logValidationErrors: true
+ evictionInterval: 10s
+ minIdleTime: 1 minute
+
+# API authentication token settings.
+authToken:
+ # The secret key used to sign API auth tokens. Changing this value will
+ # invalidate existing tokens, requiring users to re-authenticate.
+ # KEEP THIS VALUE SECRET! AN ATTACKER WITH THIS SECRET KEY WILL BE ABLE TO
+ # FORGE API AUTHENTICATION TOKENS!
+ secretKey: {{ secret_key }}
+
+ # The expiration time for auth tokens in minutes. The minimum value is 1
+ # minute, the maximum value is 43200 minutes (30 days), and the default
+ # is 1440 minutes (1 day).
+ expiration: {{ token_exp }}
+
+ # An identifier for the application that is added to authentication tokens.
+ # The default is "ContentController", it can be any arbitrary string or
+ # (ideally) a URL. Changing this value will invalidate existing tokens,
+ # requiring users to re-authenticate.
+ issuer: {{ issuer }}
+
+ # The maximum number of times a user can attempt to log in before their
+ # account is locked.
+ maxLoginAttempts: {{ maxLoginAttempts }}
+
+shareReportToken:
+ # The secret key used to sign API auth tokens. Changing this value will
+ # invalidate existing tokens, requiring users to re-authenticate.
+ # KEEP THIS VALUE SECRET! AN ATTACKER WITH THIS SECRET KEY WILL BE ABLE TO
+ # FORGE API AUTHENTICATION TOKENS!
+ secretKey: {{ secret_key }}
+
+ # The expiration time for auth tokens in minutes. The minimum value is 1
+ # minute, the maximum value is 43200 minutes (30 days), and the default
+ # is 1440 minutes (1 day).
+ expiration: {{ token_exp }}
+
+ # An identifier for the application that is added to authentication tokens.
+ # The default is "ContentController", it can be any arbitrary string or
+ # (ideally) a URL. Changing this value will invalidate existing tokens,
+ # requiring users to re-authenticate.
+ issuer: {{ issuer }}
+
+{% if is_3p3_or_later %}
+deepLinkingToken:
+ # The secret key used to sign API auth tokens. Changing this value will
+ # invalidate existing tokens, requiring users to re-authenticate.
+ # KEEP THIS VALUE SECRET! AN ATTACKER WITH THIS SECRET KEY WILL BE ABLE TO
+ # FORGE API AUTHENTICATION TOKENS!
+ secretKey: {{ secret_key }}
+
+ # The expiration time for auth tokens in minutes. The minimum value is 1
+ # minute, the maximum value is 43200 minutes (30 days), and the default
+ # is 1440 minutes (1 day).
+ expiration: {{ token_exp }}
+
+ # An identifier for the application that is added to authentication tokens.
+ # The default is "ContentController", it can be any arbitrary string or
+ # (ideally) a URL. Changing this value will invalidate existing tokens,
+ # requiring users to re-authenticate.
+ issuer: {{ issuer }}
+{% endif %}
+
+# We still need this supported in 3.3
+{% if not is_4p0_or_later %}
+{% if enable_lead_in_page is defined and enable_lead_in_page|bool == True %}
+leadInPageToken:
+ # The secret key used to sign Lead In Page JWTs. Changing this value will
+ # invalidate existing tokens.
+ secretKey: {{ secret_key }}
+
+ # The expiration time for Lead In Page JWTs in minutes. The minimum value is 1
+ # minute, the maximum value is 43200 minutes (30 days), and the default
+ # is 1440 minutes (1 day).
+ expiration: {{ token_exp }}
+
+ # An identifier for the application that is added to authentication tokens.
+ # The default is "ContentController", it can be any arbitrary string or
+ # (ideally) a URL.
+ issuer: {{ issuer }}
+
+leadInPageFeatureEnabled: {{ enable_lead_in_page }}
+{% endif %}
+{% endif %}
+
+{% if is_4p0_or_later %}
+leadInPageToken:
+ # The secret key used to sign Lead In Page JWTs. Changing this value will
+ # invalidate existing tokens.
+ secretKey: {{ secret_key }}
+
+ # The expiration time for Lead In Page JWTs in minutes. The minimum value is 1
+ # minute, the maximum value is 43200 minutes (30 days), and the default
+ # is 1440 minutes (1 day).
+ expiration: {{ token_exp }}
+
+ # An identifier for the application that is added to authentication tokens.
+ # The default is "ContentController", it can be any arbitrary string or
+ # (ideally) a URL.
+ issuer: {{ issuer }}
+{% endif %}
+
+# Dispatch-related configuration settings.
+dispatch:
+ # How long should dispatch download links be available before expiring, in minutes.
+ # The minimum value is 1 minute, and the maximum value is 720000 minutes (500 days).
+ defaultDownloadExpiration: {{ defaultDownloadExpiration }}
+
+# Engine API connection settings.
+engine:
+ # The username and password for connecting to the Engine API, as set in Engine's configuration.
+ clientUser: {{ engine_user }}
+ clientPassword: {{ engine_password }}
+
+ # The Engine API endpoint URL.
+{% if engine_use_v2_api|bool == True %}
+ url: http://localhost:8081/ScormEngineInterface/api/
+{% else %}
+ url: http://localhost:8081/ScormEngineInterface/api/v1/
+{% endif %}
+
+ # The Engine TinCan endpoint URL.
+ tincanEndpoint: http://localhost:8081/ScormEngineInterface/TCAPI/
+
+ # The username and password Engine should use to access the service layer's
+ # /engine resources. These values must match the "ApiRollupRegistrationAuthUser"
+ # and "ApiRollupRegistrationAuthPassword" properties in Engine's properties file.
+ serviceUser: {{ engine_user }}
+ servicePassword: {{ engine_password }}
+
+# HTTP Client configuration settings. These are used for making dispatch launch requests to Engine.
+jerseyClient:
+ # The The maximum idle time for a connection, once established.
+ timeout: {{ jerseyClientTimeout }}
+ # The maximum time to wait for a connection to open.
+ connectionTimeout: {{ jerseyClientConnectionTimeout }}
+
+# File storage settings. Content controller can use the local filesystem or S3 to store files.
+# If using S3, then set type to "s3", and fill in the additional settings below.
+fileStorage:
+
+{% if S3FileStorageEnabled %}
+ type: s3 # Either "local" or "s3".
+ # S3 file storage settings.
+ s3:
+ # The S3 bucket to store files in.
+ bucket: {{ S3FileStorageBucket }}
+ # Disable DNS buckets (optional - default `false`)
+ disableDnsBuckets: {{ cc_s3_disable_dns_buckets }}
+ # The path to a (local) directory for temporary files.
+ tempDir: {{ S3FileStorageTempDir }}
+ {% if not is_4p0_or_later %}
+ # The AWS access key to use for connecting to S3.
+ awsId: {{ S3FileStorageAwsId }}
+ # The AWS secret key to use for connecting to S3.
+ awsKey: {{ S3FileStorageAwsKey }}
+ # The S3 endpoint to connect to (optional - default `s3.amazonaws.com`)
+ endpoint: {{ S3Endpoint }}
+ # The S3 endpoint HTTP port (optional - default `80`)
+ httpPort: {{ cc_s3_http_port }}
+ # The S3 endpoint HTTPS port (optional - default `443`)
+ httpsPort: {{ cc_s3_https_port }}
+ # Force HTTPS only (optional - default `true`)
+ httpsOnly: {{ cc_s3_https_only }}
+{% if video_transcode_threshold is defined %}
+ # The AWS region set for the file storage
+ awsRegion: {{ S3FileStorageRegion }}
+ # The IAM role that is used when creating MediaConvert service jobs
+ mediaConvertServiceRole: {{ S3MediaConvertServiceRole }}
+{% endif %}
+{% if video_transcode_threshold is defined %}
+videoTranscodeThreshold: {{ video_transcode_threshold }}
+{% endif %}
+{% if video_transcode_tags is defined %}
+videoTranscodeTags: {{ video_transcode_tags }}
+{% endif %}
+{% if video_transcode_queue is defined %}
+videoTranscodeQueue: {{ video_transcode_queue }}
+{% endif %}
+{% endif %}
+
+# Content upload and import settings.
+import:
+ # Path to which uploaded files should be saved (e.g. "/mnt/content/uploads"). This path is always
+ # on the local filesystem, even if "fileStorage" is configured to use S3!
+ filePathToUploads: {{ uploads_filepath }}
+ # Path to which imported content should be saved (e.g. "/mnt/content/courses"). This path will
+ # be on either the local filesystem or S3, depending on the "fileStorage" settings.
+ filePathToContent: {{ courses_webpath }}
+ # Web path under which imported content should be served (default is "/courses").
+ webPathToContent: {{ courses_webpath }}
+
+ # Path to which generated version export zip files are located. This path will
+ # be on either the local filesystem or S3, depending on the "fileStorage" settings.
+ filePathToGeneratedZips: {{ generated_course_zip_webpath }}
+ daysToKeepExportedVersionZips: {{ daysToKeepExportedVersionZips }}
+
+
+
+
+{% elif not S3FileStorageEnabled %}
+ type: local
+
+import:
+ # Path to which uploaded files should be saved (e.g. "/mnt/content/uploads"). This path is always
+ # on the local filesystem, even if "fileStorage" is configured to use S3!
+ filePathToUploads: {{ uploads_filepath }}
+ # Path to which imported content should be saved (e.g. "/mnt/content/courses"). This path will
+ # be on either the local filesystem or S3, depending on the "fileStorage" settings.
+ filePathToContent: {{ courses_filepath }}
+ # Web path under which imported content should be served (default is "/courses").
+ webPathToContent: {{ courses_webpath }}
+
+ # Path to which generated version export zip files are located. This path will
+ # be on either the local filesystem or S3, depending on the "fileStorage" settings.
+ filePathToGeneratedZips: {{ generated_course_zip_location }}
+ daysToKeepExportedVersionZips: {{ daysToKeepExportedVersionZips }}
+
+{% endif %}
+
+ # The number of background threads to use for import jobs.
+ importThreads: {{ import_threads }}
+
+ # During the import process, the application has to generate "signed URLs" for package
+ # manifest files. These settings control how those URLs are generated.
+ manifestUrlSigning:
+ # The secret key used to sign manifest URLs. KEEP THIS VALUE SECRET! AN ATTACKER WITH
+ # THIS SECRET KEY WILL BE ABLE TO FORGE SIGNED MANIFEST URLS!
+ secretKey: {{ secret_key }}
+
+ # The expiration time for manifest URLs in minutes. The minimum value is 1 minute, the
+ # maximum value is 480 minutes (8 hours), and the default is 10 minutes.
+ #expiration: 10
+
+ # An identifier for the application that is included in the manifest URL signature.
+ # The default is "ContentController", it can be any arbitrary string or (ideally) a URL.
+ issuer: {{ issuer }}
+
+{% if use_cloudfront is defined and use_cloudfront|bool == True %}
+# CloudFront signed cookie settings.
+cloudFront:
+ # The domain name to use for policy statements.
+ resourceDomain: {{ cloudfront_distro_domain }}
+ # The absolute file path to the PRIVATE key file to use for signing.
+ keyFilePath: {{ cc_deploy_path }}/bin/{{ cloudfront_private_key }}
+{% if cloudfront_access_key_id != None %}
+ # The ID for the CloudFront key pair that is being used for signing.
+ keyPairId: {{ cloudfront_access_key_id }}
+{% endif %}
+ # How long the cookies should be valid in minutes. This should be greater than the maximum
+ # amount of time you expect learners to have courses open from a single launch. The default
+ # is 1440 minutes (1 day), and the maximum is 43200 minutes (30 days).
+ expiration: 1440
+{% endif %}
+
+logging:
+ level: INFO
+ loggers:
+ "com.rusticisoftware.contentcontroller.service.cli.webpath":
+ level: DEBUG
+ appenders:
+ - type: console
+ # Level Thread Date Logger Message (\n Exception)
+ logFormat: "%-5p %-10t [%d{HH:mm:ss.SSS}] %c{0}: %m%n%rEx"
+ "com.rusticisoftware.contentcontroller.service.audit.LoggingResourceAuditor":
+ additive: {{ audit_log_failure|default(false) }}
+ appenders:
+ - type: file
+ currentLogFilename: /var/log/contentcontroller-audit.log
+ archivedLogFilenamePattern: /var/log/contentcontroller-audit-%d.log
+ archivedFileCount: {{ audit_log_archive_days|default('32') }}
+{% for logger_name, log_level in cc_custom_log_levels.items() %}
+ "{{ logger_name }}":
+ level: {{ log_level }}
+{% endfor %}
+ appenders:
+ - type: file
+ threshold: ALL
+ currentLogFilename: /var/log/contentcontroller.log
+ archivedLogFilenamePattern: /var/log/contentcontroller-%d.log.gz
+ archivedFileCount: 3
+
+{% if use_datadog %}
+
+metrics:
+ frequency: 1 minute # Default is 1 second.
+ reporters:
+ - type: datadog
+ host: https://app.datadoghq.com/api/ # Optional with UDP Transport
+ #tags: # Optional. Defaults to (empty)
+ #includes: [] # Optional. Defaults to (all).
+ #excludes: # Optional. Defaults to (none).
+ prefix: {{ ClientName }} # Optional. Defaults to (none).
+ #expansions: # Optional. Defaults to (all).
+ #metricNameFormatter: # Optional. Default is "default".
+ #dynamicTagsCallback: # Optional. Defaults to (none).
+ transport:
+ type: http
+ apiKey: {{ datadog_api_key }}
+ #connectTimeout: 5 # Optional. Default is 5 seconds
+ #socketTimeout: 5 # Optional. Default is 5 seconds
+
+{% endif %}
+
+
+reporting:
+ sanitizeLearnerInfo: {{ sanitizeLearnerInfo }}
+ # Location on the file system that generated report files should be stored
+{% if S3FileStorageEnabled %}
+ generatedFileLocation: {{ generated_file_webpath }}
+{% elif not S3FileStorageEnabled %}
+ generatedFileLocation: {{ generated_file_location }}
+{% endif %}
+
+
+{% if enable_automation_api %}
+
+publicApi:
+ enabled: true
+ accessToken:
+ # The secret key used to sign API auth tokens. Changing this value will
+ # invalidate existing tokens, requiring users to re-authenticate.
+ # KEEP THIS VALUE SECRET! AN ATTACKER WITH THIS SECRET KEY WILL BE ABLE TO
+ # FORGE API AUTHENTICATION TOKENS!
+ secretKey: {{ secret_key }}
+
+ # The expiration time for auth tokens in minutes. The minimum value is 1
+ # minute, the maximum value is 43200 minutes (30 days), and the default
+ # is 30 minutes. (Optional)
+ expiration: {{ public_api_auth_expiration|default('30') }}
+
+ # An identifier for the application that is added to authentication tokens.
+ # The default is "ContentControllerApi", it can be any arbitrary string or
+ # (ideally) a URL. Changing this value will invalidate existing tokens,
+ # requiring users to re-authenticate. (Optional)
+ issuer: ContentControllerApi
+
+{% endif %}
+
+contentvault:
+ enabled: {{ enable_contentvault }}
+ expirationSeconds: {{ contentvault_expiration_seconds }}
+ slidingWindowSeconds: {{ contentvault_sliding_window_seconds }}
+ windowCacheDuration: {{ contentvault_window_cache_duration }}
+ validationOptions: {{ contentvault_fields_to_validate }}
+{% if contentvault_inject_segment_media_files is defined %}
+ injectSegmentForMediaFiles: {{ contentvault_inject_segment_media_files}}
+{% endif %}
+
+{% if enable_emails %}
+email:
+ enabled: true # Allow Content Controller to send emails
+ host: {{ cc_smtp_host }} # Defaults to localhost (optional)
+ port: {{ cc_smtp_port }} # Defaults to 25 (optional)
+ username: {{ cc_smtp_username }} # (optional)
+ password: {{ cc_smtp_password }} # (optional)
+ transport: {{ cc_smtp_transport }} # One of ['SSL', 'TLS', 'PLAIN'] - Defaults to 'PLAIN' (optional)
+ fromUserName: {{ cc_smtp_from_name }} # Default from name for emails - Defaults to 'Content Controller' (optional)
+ fromUserEmail: {{ cc_smtp_from_email }} # Default from email address - Defaults to 'no-reply@contentcontroller.com' (optional)
+
+ {% if password_reset_failure_threshold is defined %}
+ # The maximum number of times a user can attempt to log in before their
+ # account is locked.
+ passwordResetThreshold: {{ password_reset_failure_threshold }}
+ {% endif %}
+
+ {% if password_reset_failure_window is defined %}
+ # Number indicates the past number of days to check the password_reset_failure_threshold against requests made
+ passwordResetWindow: {{ password_reset_failure_window }}
+ {% endif %}
+{% endif %}
+
+{% if user_account_days_inactive_threshold is defined %}
+userAccountDaysInactiveThreshold: {{ user_account_days_inactive_threshold }}
+{% endif %}
+
+{% if assign_latest_version_instead_of_restart is defined %}
+assignLatestVersionInsteadOfRestart: {{ assign_latest_version_instead_of_restart }}
+{% endif %}
+
+{% if enable_signin_customization is defined %}
+signInCustomizationEnabled: {{ enable_signin_customization }}
+{% endif %}
+
+{% if enable_saml is defined and enable_saml|bool == True %}
+saml:
+ enabled: {{ enable_saml }}
+ keystorePassword: {{ keystore_password }}
+ privateKeyPassword: {{ private_key_password }}
+ maximumAuthLifetime: {{ maximum_auth_lifetime }}
+ includeQueryParamOnCallback: {{ include_query_param_on_callback if include_query_param_on_callback is defined else true }}
+{% if saml_identifying_attribute is defined %}
+ identifyingAttribute: {{ saml_identifying_attribute }}
+{% endif %}
+{% if saml_access_attribute is defined and saml_access_value is defined %}
+ accessAttribute: {{ saml_access_attribute }}
+ accessValue: {{ saml_access_value }}
+ accessCondition: {% if saml_access_condition is defined %}{{ saml_access_condition }}{% else %}contains{% endif %}
+{% endif %}
+{% endif %}
+
+# These conditionals let us only define the requirements specified in the config file,
+# and we default
+{% if passwords_require_number is defined
+ or passwords_require_special is defined
+ or passwords_require_letter is defined
+ or passwords_require_lowercase is defined
+ or passwords_require_uppercase is defined
+ or passwords_minimum_length is defined %}
+passwords:
+ requireNumber: {{ passwords_require_number if passwords_require_number is defined else true }}
+ requireLetter: {{ passwords_require_letter if passwords_require_letter is defined else true }}
+ requireSpecial: {{ passwords_require_special if passwords_require_special is defined else false }}
+ requireLowercase: {{ passwords_require_lowercase if passwords_require_lowercase is defined else false }}
+ requireUppercase: {{ passwords_require_uppercase if passwords_require_uppercase is defined else false }}
+ {% if passwords_minimum_length is defined %}minimumLength: {{ passwords_minimum_length }} {% endif %}
+{% endif %}
+
+# Determines whether or not the launcher link is an account option
+{% if use_launcher_link is defined %}
+launcherLinkEnabled: {{ use_launcher_link }}
+{% endif %}
+
+{% if audit_log_failure is defined %}
+auditLogFailureCheckEnabled: {{ audit_log_failure }}
+{% endif %}
+
+{% if is_3p3_or_later and deep_linking_secret is defined %}
+lti13DeepLinkingSecret: {{ deep_linking_secret }}
+{% endif %}
+
+{% if enable_webhooks_service is defined and enable_webhooks_service|bool %}
+webhooksConfiguration:
+ messageQueueName: {{ message_queue_name }}
+ deadLetterQueueName: {{ dead_letter_queue_name }}
+ testingServiceEnabled: {{ enable_testing_service }}
+ postbackAuthSecret: {{ webhooks_auth_secret }}
+ enableQueuePolling: {{ enable_queue_polling }}
+ pollingInterval: {{ queue_polling_interval }}
+{% if testing_service_collection_id is defined %}
+ testingServiceCollectionId: {{ testing_service_collection_id }}
+{% endif %}
+{% endif %}
+
+{% if bypass_db_connection_health_check is defined %}
+bypassDbConnectionHealthCheck: {{ bypass_db_connection_health_check }}
+{% endif %}
+
+{% if is_4p0_or_later %}
+{% if enable_aws_connection is defined and enable_aws_connection|bool %}
+awsConfiguration:
+ # The AWS access key to use for connecting to S3.
+ awsId: {{ S3FileStorageAwsId }}
+ # The AWS secret key to use for connecting to S3.
+ awsKey: {{ S3FileStorageAwsKey }}
+ # The S3 endpoint to connect to (optional - default `s3.amazonaws.com`)
+ endpoint: {{ S3Endpoint }}
+ # The S3 endpoint HTTP port (optional - default `80`)
+ httpPort: {{ cc_s3_http_port }}
+ # The S3 endpoint HTTPS port (optional - default `443`)
+ httpsPort: {{ cc_s3_https_port }}
+ # Force HTTPS only (optional - default `true`)
+ httpsOnly: {{ cc_s3_https_only }}
+ # The AWS region set for the file storage
+ awsRegion: {{ S3FileStorageRegion }}
+{% endif %}
+{% if video_transcode_threshold is defined %}
+transcoderConfiguration:
+ videoTranscodeThreshold: {{ video_transcode_threshold }}
+ # The IAM role that is used when creating MediaConvert service jobs
+ mediaConvertServiceRole: {{ media_convert_service_role }}
+{% if video_transcode_tags is defined %}
+ videoTranscodeTags: {{ video_transcode_tags }}
+{% endif %}
+{% if video_transcode_queue is defined %}
+ videoTranscodeQueue: {{ video_transcode_queue }}
+{% endif %}
+{% endif %}
+{% endif %}
+
+{% if multicourse_imports is defined %}
+enableMultiCourseImports: {{ multicourse_imports }}
+{% endif %}
+
+# This enables an API for manually executing some tasks while in development.
+# This is helpful for such things as firing off License Alert emails manually
+# instead of waiting for the scheduler to pick them up.
+
+enableDebugApi: {{ enable_debug_api if enable_debug_api is defined else false }}
\ No newline at end of file
diff --git a/roles/content-controller/templates/contentsample.html.j2 b/roles/content-controller/templates/contentsample.html.j2
new file mode 100644
index 0000000..074f098
--- /dev/null
+++ b/roles/content-controller/templates/contentsample.html.j2
@@ -0,0 +1,72 @@
+
+
+
+ Rustici Cross Domain Content Sample
+
+
+
+
+
+
+
+
+
+
+
+
+
This is remote content served from a different domain...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/roles/content-controller/vars/main.yml b/roles/content-controller/vars/main.yml
new file mode 100644
index 0000000..b1d9846
--- /dev/null
+++ b/roles/content-controller/vars/main.yml
@@ -0,0 +1,33 @@
+---
+
+cc_deploy_path: "/var/lib/contentcontroller"
+
+CC_API_PORT: 8989
+CC_ADMIN_PORT: 8981
+
+# When the static assets go:
+
+# Apache Settings (with trailing slash, please)
+html_path: "/var/www/html/"
+
+# The maximum number of times a user can attempt to log in before their
+# account is locked.
+maxLoginAttempts: 3
+
+# How long should dispatch download links be available before expiring, in minutes.
+# The minimum value is 1 minute, and the maximum value is 720000 minutes (500 days).
+defaultDownloadExpiration: 20160
+
+# An identifier for the application that is added to authentication tokens.
+# The default is "ContentController", it can be any arbitrary string or
+# (ideally) a URL. Changing this value will invalidate existing tokens,
+# requiring users to re-authenticate.
+issuer: ContentController
+
+# A temp directory where we can stage stuff during installation
+temp_path: /tmp/contentcontroller-snapshot
+
+# The hostname at which the service layer itself is hosted. Typically, this will be localhost.
+# This setting is used when importing content, but is not persisted
+# anywhere - so it may be changed without causing any issues.
+serviceHost: localhost
diff --git a/roles/fips/defaults/main.yml b/roles/fips/defaults/main.yml
new file mode 100644
index 0000000..60aa19c
--- /dev/null
+++ b/roles/fips/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+## FIPS Support defaults to False
+fips_support_enabled: false
\ No newline at end of file
diff --git a/roles/fips/files/bc-fips-1.0.2.3.jar b/roles/fips/files/bc-fips-1.0.2.3.jar
new file mode 100644
index 0000000..1a8c5de
Binary files /dev/null and b/roles/fips/files/bc-fips-1.0.2.3.jar differ
diff --git a/roles/fips/files/bcpkix-fips-1.0.7.jar b/roles/fips/files/bcpkix-fips-1.0.7.jar
new file mode 100644
index 0000000..2c56b09
Binary files /dev/null and b/roles/fips/files/bcpkix-fips-1.0.7.jar differ
diff --git a/roles/fips/files/java.security b/roles/fips/files/java.security
new file mode 100644
index 0000000..488a736
--- /dev/null
+++ b/roles/fips/files/java.security
@@ -0,0 +1,1319 @@
+#
+# This is the "master security properties file".
+#
+# An alternate java.security properties file may be specified
+# from the command line via the system property
+#
+# -Djava.security.properties=
+#
+# This properties file appends to the master security properties file.
+# If both properties files specify values for the same key, the value
+# from the command-line properties file is selected, as it is the last
+# one loaded.
+#
+# Also, if you specify
+#
+# -Djava.security.properties== (2 equals),
+#
+# then that properties file completely overrides the master security
+# properties file.
+#
+# To disable the ability to specify an additional properties file from
+# the command line, set the key security.overridePropertiesFile
+# to false in the master security properties file. It is set to true
+# by default.
+
+# In this file, various security properties are set for use by
+# java.security classes. This is where users can statically register
+# Cryptography Package Providers ("providers" for short). The term
+# "provider" refers to a package or set of packages that supply a
+# concrete implementation of a subset of the cryptography aspects of
+# the Java Security API. A provider may, for example, implement one or
+# more digital signature algorithms or message digest algorithms.
+#
+# Each provider must implement a subclass of the Provider class.
+# To register a provider in this master security properties file,
+# specify the Provider subclass name and priority in the format
+#
+# security.provider.=
+#
+# This declares a provider, and specifies its preference
+# order n. The preference order is the order in which providers are
+# searched for requested algorithms (when no specific provider is
+# requested). The order is 1-based; 1 is the most preferred, followed
+# by 2, and so on.
+#
+# must specify the subclass of the Provider class whose
+# constructor sets the values of various properties that are required
+# for the Java Security API to look up the algorithms or other
+# facilities implemented by the provider.
+#
+# There must be at least one provider specification in java.security.
+# There is a default provider that comes standard with the JDK. It
+# is called the "SUN" provider, and its Provider subclass
+# named Sun appears in the sun.security.provider package. Thus, the
+# "SUN" provider is registered via the following:
+#
+# security.provider.1=sun.security.provider.Sun
+#
+# (The number 1 is used for the default provider.)
+#
+# Note: Providers can be dynamically registered instead by calls to
+# either the addProvider or insertProviderAt method in the Security
+# class.
+
+#
+# List of providers and their preference orders (see above):
+#
+security.provider.1=sun.security.provider.Sun
+security.provider.2=sun.security.rsa.SunRsaSign
+security.provider.3=sun.security.ec.SunEC
+security.provider.4=com.sun.net.ssl.internal.ssl.Provider
+security.provider.5=com.sun.crypto.provider.SunJCE
+security.provider.6=sun.security.jgss.SunProvider
+security.provider.7=com.sun.security.sasl.Provider
+security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
+security.provider.9=sun.security.smartcardio.SunPCSC
+
+#
+# Security providers used when FIPS mode support is active
+#
+fips.provider.1=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
+fips.provider.2=com.sun.net.ssl.internal.ssl.Provider BCFIPS
+fips.provider.3=sun.security.provider.Sun
+fips.provider.4=sun.security.pkcs11.SunPKCS11 ${java.home}/lib/security/nss.fips.cfg
+fips.provider.=sun.security.ec.SunEC
+
+#
+# Sun Provider SecureRandom seed source.
+#
+# Select the primary source of seed data for the "SHA1PRNG" and
+# "NativePRNG" SecureRandom implementations in the "Sun" provider.
+# (Other SecureRandom implementations might also use this property.)
+#
+# On Unix-like systems (for example, Solaris/Linux/MacOS), the
+# "NativePRNG" and "SHA1PRNG" implementations obtains seed data from
+# special device files such as file:/dev/random.
+#
+# On Windows systems, specifying the URLs "file:/dev/random" or
+# "file:/dev/urandom" will enable the native Microsoft CryptoAPI seeding
+# mechanism for SHA1PRNG.
+#
+# By default, an attempt is made to use the entropy gathering device
+# specified by the "securerandom.source" Security property. If an
+# exception occurs while accessing the specified URL:
+#
+# SHA1PRNG:
+# the traditional system/thread activity algorithm will be used.
+#
+# NativePRNG:
+# a default value of /dev/random will be used. If neither
+# are available, the implementation will be disabled.
+# "file" is the only currently supported protocol type.
+#
+# The entropy gathering device can also be specified with the System
+# property "java.security.egd". For example:
+#
+# % java -Djava.security.egd=file:/dev/random MainClass
+#
+# Specifying this System property will override the
+# "securerandom.source" Security property.
+#
+# In addition, if "file:/dev/random" or "file:/dev/urandom" is
+# specified, the "NativePRNG" implementation will be more preferred than
+# SHA1PRNG in the Sun provider.
+#
+securerandom.source=file:/dev/random
+
+#
+# A list of known strong SecureRandom implementations.
+#
+# To help guide applications in selecting a suitable strong
+# java.security.SecureRandom implementation, Java distributions should
+# indicate a list of known strong implementations using the property.
+#
+# This is a comma-separated list of algorithm and/or algorithm:provider
+# entries.
+#
+securerandom.strongAlgorithms=NativePRNGBlocking:SUN
+
+#
+# Class to instantiate as the javax.security.auth.login.Configuration
+# provider.
+#
+login.configuration.provider=sun.security.provider.ConfigFile
+
+#
+# Default login configuration file
+#
+#login.config.url.1=file:${user.home}/.java.login.config
+
+#
+# Class to instantiate as the system Policy. This is the name of the class
+# that will be used as the Policy object.
+#
+policy.provider=sun.security.provider.PolicyFile
+
+# The default is to have a single system-wide policy file,
+# and a policy file in the user's home directory.
+policy.url.1=file:${java.home}/lib/security/java.policy
+policy.url.2=file:${user.home}/.java.policy
+
+# whether or not we expand properties in the policy file
+# if this is set to false, properties (${...}) will not be expanded in policy
+# files.
+policy.expandProperties=true
+
+# whether or not we allow an extra policy to be passed on the command line
+# with -Djava.security.policy=somefile. Comment out this line to disable
+# this feature.
+policy.allowSystemProperty=true
+
+# whether or not we look into the IdentityScope for trusted Identities
+# when encountering a 1.1 signed JAR file. If the identity is found
+# and is trusted, we grant it AllPermission.
+policy.ignoreIdentityScope=false
+
+#
+# Default keystore type.
+#
+keystore.type=jks
+
+#
+# Default keystore type used when global crypto-policies are set to FIPS.
+#
+fips.keystore.type=PKCS11
+
+#
+# Controls compatibility mode for the JKS keystore type.
+#
+# When set to 'true', the JKS keystore type supports loading
+# keystore files in either JKS or PKCS12 format. When set to 'false'
+# it supports loading only JKS keystore files.
+#
+keystore.type.compat=true
+
+#
+# List of comma-separated packages that start with or equal this string
+# will cause a security exception to be thrown when
+# passed to checkPackageAccess unless the
+# corresponding RuntimePermission ("accessClassInPackage."+package) has
+# been granted.
+package.access=sun.,\
+ com.sun.xml.internal.,\
+ com.sun.imageio.,\
+ com.sun.istack.internal.,\
+ com.sun.jmx.,\
+ com.sun.media.sound.,\
+ com.sun.naming.internal.,\
+ com.sun.proxy.,\
+ com.sun.corba.se.,\
+ com.sun.org.apache.bcel.internal.,\
+ com.sun.org.apache.regexp.internal.,\
+ com.sun.org.apache.xerces.internal.,\
+ com.sun.org.apache.xpath.internal.,\
+ com.sun.org.apache.xalan.internal.extensions.,\
+ com.sun.org.apache.xalan.internal.lib.,\
+ com.sun.org.apache.xalan.internal.res.,\
+ com.sun.org.apache.xalan.internal.templates.,\
+ com.sun.org.apache.xalan.internal.utils.,\
+ com.sun.org.apache.xalan.internal.xslt.,\
+ com.sun.org.apache.xalan.internal.xsltc.cmdline.,\
+ com.sun.org.apache.xalan.internal.xsltc.compiler.,\
+ com.sun.org.apache.xalan.internal.xsltc.trax.,\
+ com.sun.org.apache.xalan.internal.xsltc.util.,\
+ com.sun.org.apache.xml.internal.res.,\
+ com.sun.org.apache.xml.internal.resolver.helpers.,\
+ com.sun.org.apache.xml.internal.resolver.readers.,\
+ com.sun.org.apache.xml.internal.security.,\
+ com.sun.org.apache.xml.internal.serializer.utils.,\
+ com.sun.org.apache.xml.internal.utils.,\
+ com.sun.org.glassfish.,\
+ com.oracle.xmlns.internal.,\
+ com.oracle.webservices.internal.,\
+ oracle.jrockit.jfr.,\
+ org.jcp.xml.dsig.internal.,\
+ jdk.internal.,\
+ jdk.nashorn.internal.,\
+ jdk.nashorn.tools.,\
+ jdk.xml.internal.,\
+ com.sun.activation.registries.,\
+ jdk.jfr.events.,\
+ jdk.jfr.internal.,\
+ jdk.management.jfr.internal.,\
+ org.GNOME.Accessibility.,\
+ org.GNOME.Bonobo.
+
+#
+# List of comma-separated packages that start with or equal this string
+# will cause a security exception to be thrown when
+# passed to checkPackageDefinition unless the
+# corresponding RuntimePermission ("defineClassInPackage."+package) has
+# been granted.
+#
+# by default, none of the class loaders supplied with the JDK call
+# checkPackageDefinition.
+#
+package.definition=sun.,\
+ com.sun.xml.internal.,\
+ com.sun.imageio.,\
+ com.sun.istack.internal.,\
+ com.sun.jmx.,\
+ com.sun.media.sound.,\
+ com.sun.naming.internal.,\
+ com.sun.proxy.,\
+ com.sun.corba.se.,\
+ com.sun.org.apache.bcel.internal.,\
+ com.sun.org.apache.regexp.internal.,\
+ com.sun.org.apache.xerces.internal.,\
+ com.sun.org.apache.xpath.internal.,\
+ com.sun.org.apache.xalan.internal.extensions.,\
+ com.sun.org.apache.xalan.internal.lib.,\
+ com.sun.org.apache.xalan.internal.res.,\
+ com.sun.org.apache.xalan.internal.templates.,\
+ com.sun.org.apache.xalan.internal.utils.,\
+ com.sun.org.apache.xalan.internal.xslt.,\
+ com.sun.org.apache.xalan.internal.xsltc.cmdline.,\
+ com.sun.org.apache.xalan.internal.xsltc.compiler.,\
+ com.sun.org.apache.xalan.internal.xsltc.trax.,\
+ com.sun.org.apache.xalan.internal.xsltc.util.,\
+ com.sun.org.apache.xml.internal.res.,\
+ com.sun.org.apache.xml.internal.resolver.helpers.,\
+ com.sun.org.apache.xml.internal.resolver.readers.,\
+ com.sun.org.apache.xml.internal.security.,\
+ com.sun.org.apache.xml.internal.serializer.utils.,\
+ com.sun.org.apache.xml.internal.utils.,\
+ com.sun.org.glassfish.,\
+ com.oracle.xmlns.internal.,\
+ com.oracle.webservices.internal.,\
+ oracle.jrockit.jfr.,\
+ org.jcp.xml.dsig.internal.,\
+ jdk.internal.,\
+ jdk.nashorn.internal.,\
+ jdk.nashorn.tools.,\
+ jdk.xml.internal.,\
+ com.sun.activation.registries.,\
+ jdk.jfr.events.,\
+ jdk.jfr.internal.,\
+ jdk.management.jfr.internal.,\
+ org.GNOME.Accessibility.,\
+ org.GNOME.Bonobo.
+
+#
+# Determines whether this properties file can be appended to
+# or overridden on the command line via -Djava.security.properties
+#
+security.overridePropertiesFile=true
+
+#
+# Determines whether this properties file will be appended to
+# using the system properties file stored at
+# /etc/crypto-policies/back-ends/java.config
+#
+security.useSystemPropertiesFile=true
+
+#
+# Determines the default key and trust manager factory algorithms for
+# the javax.net.ssl package.
+#
+ssl.KeyManagerFactory.algorithm=SunX509
+ssl.TrustManagerFactory.algorithm=PKIX
+
+#
+# The Java-level namelookup cache policy for successful lookups:
+#
+# any negative value: caching forever
+# any positive value: the number of seconds to cache an address for
+# zero: do not cache
+#
+# default value is forever (FOREVER). For security reasons, this
+# caching is made forever when a security manager is set. When a security
+# manager is not set, the default behavior in this implementation
+# is to cache for 30 seconds.
+#
+# NOTE: setting this to anything other than the default value can have
+# serious security implications. Do not set it unless
+# you are sure you are not exposed to DNS spoofing attack.
+#
+#networkaddress.cache.ttl=-1
+
+# The Java-level namelookup cache policy for failed lookups:
+#
+# any negative value: cache forever
+# any positive value: the number of seconds to cache negative lookup results
+# zero: do not cache
+#
+# In some Microsoft Windows networking environments that employ
+# the WINS name service in addition to DNS, name service lookups
+# that fail may take a noticeably long time to return (approx. 5 seconds).
+# For this reason the default caching policy is to maintain these
+# results for 10 seconds.
+#
+#
+networkaddress.cache.negative.ttl=10
+
+#
+# Properties to configure OCSP for certificate revocation checking
+#
+
+# Enable OCSP
+#
+# By default, OCSP is not used for certificate revocation checking.
+# This property enables the use of OCSP when set to the value "true".
+#
+# NOTE: SocketPermission is required to connect to an OCSP responder.
+#
+# Example,
+# ocsp.enable=true
+
+#
+# Location of the OCSP responder
+#
+# By default, the location of the OCSP responder is determined implicitly
+# from the certificate being validated. This property explicitly specifies
+# the location of the OCSP responder. The property is used when the
+# Authority Information Access extension (defined in RFC 5280) is absent
+# from the certificate or when it requires overriding.
+#
+# Example,
+# ocsp.responderURL=http://ocsp.example.net:80
+
+#
+# Subject name of the OCSP responder's certificate
+#
+# By default, the certificate of the OCSP responder is that of the issuer
+# of the certificate being validated. This property identifies the certificate
+# of the OCSP responder when the default does not apply. Its value is a string
+# distinguished name (defined in RFC 2253) which identifies a certificate in
+# the set of certificates supplied during cert path validation. In cases where
+# the subject name alone is not sufficient to uniquely identify the certificate
+# then both the "ocsp.responderCertIssuerName" and
+# "ocsp.responderCertSerialNumber" properties must be used instead. When this
+# property is set then those two properties are ignored.
+#
+# Example,
+# ocsp.responderCertSubjectName="CN=OCSP Responder, O=XYZ Corp"
+
+#
+# Issuer name of the OCSP responder's certificate
+#
+# By default, the certificate of the OCSP responder is that of the issuer
+# of the certificate being validated. This property identifies the certificate
+# of the OCSP responder when the default does not apply. Its value is a string
+# distinguished name (defined in RFC 2253) which identifies a certificate in
+# the set of certificates supplied during cert path validation. When this
+# property is set then the "ocsp.responderCertSerialNumber" property must also
+# be set. When the "ocsp.responderCertSubjectName" property is set then this
+# property is ignored.
+#
+# Example,
+# ocsp.responderCertIssuerName="CN=Enterprise CA, O=XYZ Corp"
+
+#
+# Serial number of the OCSP responder's certificate
+#
+# By default, the certificate of the OCSP responder is that of the issuer
+# of the certificate being validated. This property identifies the certificate
+# of the OCSP responder when the default does not apply. Its value is a string
+# of hexadecimal digits (colon or space separators may be present) which
+# identifies a certificate in the set of certificates supplied during cert path
+# validation. When this property is set then the "ocsp.responderCertIssuerName"
+# property must also be set. When the "ocsp.responderCertSubjectName" property
+# is set then this property is ignored.
+#
+# Example,
+# ocsp.responderCertSerialNumber=2A:FF:00
+
+#
+# Policy for failed Kerberos KDC lookups:
+#
+# When a KDC is unavailable (network error, service failure, etc), it is
+# put inside a blacklist and accessed less often for future requests. The
+# value (case-insensitive) for this policy can be:
+#
+# tryLast
+# KDCs in the blacklist are always tried after those not on the list.
+#
+# tryLess[:max_retries,timeout]
+# KDCs in the blacklist are still tried by their order in the configuration,
+# but with smaller max_retries and timeout values. max_retries and timeout
+# are optional numerical parameters (default 1 and 5000, which means once
+# and 5 seconds). Please notes that if any of the values defined here is
+# more than what is defined in krb5.conf, it will be ignored.
+#
+# Whenever a KDC is detected as available, it is removed from the blacklist.
+# The blacklist is reset when krb5.conf is reloaded. You can add
+# refreshKrb5Config=true to a JAAS configuration file so that krb5.conf is
+# reloaded whenever a JAAS authentication is attempted.
+#
+# Example,
+# krb5.kdc.bad.policy = tryLast
+# krb5.kdc.bad.policy = tryLess:2,2000
+krb5.kdc.bad.policy = tryLast
+
+#
+# Kerberos cross-realm referrals (RFC 6806)
+#
+# OpenJDK's Kerberos client supports cross-realm referrals as defined in
+# RFC 6806. This allows to setup more dynamic environments in which clients
+# do not need to know in advance how to reach the realm of a target principal
+# (either a user or service).
+#
+# When a client issues an AS or a TGS request, the "canonicalize" option
+# is set to announce support of this feature. A KDC server may fulfill the
+# request or reply referring the client to a different one. If referred,
+# the client will issue a new request and the cycle repeats.
+#
+# In addition to referrals, the "canonicalize" option allows the KDC server
+# to change the client name in response to an AS request. For security reasons,
+# RFC 6806 (section 11) FAST scheme is enforced.
+#
+# Disable Kerberos cross-realm referrals. Value may be overwritten with a
+# System property (-Dsun.security.krb5.disableReferrals).
+sun.security.krb5.disableReferrals=false
+
+# Maximum number of AS or TGS referrals to avoid infinite loops. Value may
+# be overwritten with a System property (-Dsun.security.krb5.maxReferrals).
+sun.security.krb5.maxReferrals=5
+
+#
+# This property contains a list of disabled EC Named Curves that can be included
+# in the jdk.[tls|certpath|jar].disabledAlgorithms properties. To include this
+# list in any of the disabledAlgorithms properties, add the property name as
+# an entry.
+jdk.disabled.namedCurves = secp256k1
+
+#
+# Algorithm restrictions for certification path (CertPath) processing
+#
+# In some environments, certain algorithms or key lengths may be undesirable
+# for certification path building and validation. For example, "MD2" is
+# generally no longer considered to be a secure hash algorithm. This section
+# describes the mechanism for disabling algorithms based on algorithm name
+# and/or key length. This includes algorithms used in certificates, as well
+# as revocation information such as CRLs and signed OCSP Responses.
+# The syntax of the disabled algorithm string is described as follows:
+# DisabledAlgorithms:
+# " DisabledAlgorithm { , DisabledAlgorithm } "
+#
+# DisabledAlgorithm:
+# AlgorithmName [Constraint] { '&' Constraint } | IncludeProperty
+#
+# AlgorithmName:
+# (see below)
+#
+# Constraint:
+# KeySizeConstraint | CAConstraint | DenyAfterConstraint |
+# UsageConstraint
+#
+# KeySizeConstraint:
+# keySize Operator KeyLength
+#
+# Operator:
+# <= | < | == | != | >= | >
+#
+# KeyLength:
+# Integer value of the algorithm's key length in bits
+#
+# CAConstraint:
+# jdkCA
+#
+# DenyAfterConstraint:
+# denyAfter YYYY-MM-DD
+#
+# UsageConstraint:
+# usage [TLSServer] [TLSClient] [SignedJAR]
+#
+# IncludeProperty:
+# include
+#
+# The "AlgorithmName" is the standard algorithm name of the disabled
+# algorithm. See "Java Cryptography Architecture Standard Algorithm Name
+# Documentation" for information about Standard Algorithm Names. Matching
+# is performed using a case-insensitive sub-element matching rule. (For
+# example, in "SHA1withECDSA" the sub-elements are "SHA1" for hashing and
+# "ECDSA" for signatures.) If the assertion "AlgorithmName" is a
+# sub-element of the certificate algorithm name, the algorithm will be
+# rejected during certification path building and validation. For example,
+# the assertion algorithm name "DSA" will disable all certificate algorithms
+# that rely on DSA, such as NONEwithDSA, SHA1withDSA. However, the assertion
+# will not disable algorithms related to "ECDSA".
+#
+# The "IncludeProperty" allows a implementation-defined security property that
+# can be included in the disabledAlgorithms properties. These properties are
+# to help manage common actions easier across multiple disabledAlgorithm
+# properties.
+# There is one defined security property: jdk.disabled.NamedCurves
+# See the property for more specific details.
+#
+#
+# A "Constraint" defines restrictions on the keys and/or certificates for
+# a specified AlgorithmName:
+#
+# KeySizeConstraint:
+# keySize Operator KeyLength
+# The constraint requires a key of a valid size range if the
+# "AlgorithmName" is of a key algorithm. The "KeyLength" indicates
+# the key size specified in number of bits. For example,
+# "RSA keySize <= 1024" indicates that any RSA key with key size less
+# than or equal to 1024 bits should be disabled, and
+# "RSA keySize < 1024, RSA keySize > 2048" indicates that any RSA key
+# with key size less than 1024 or greater than 2048 should be disabled.
+# This constraint is only used on algorithms that have a key size.
+#
+# CAConstraint:
+# jdkCA
+# This constraint prohibits the specified algorithm only if the
+# algorithm is used in a certificate chain that terminates at a marked
+# trust anchor in the lib/security/cacerts keystore. If the jdkCA
+# constraint is not set, then all chains using the specified algorithm
+# are restricted. jdkCA may only be used once in a DisabledAlgorithm
+# expression.
+# Example: To apply this constraint to SHA-1 certificates, include
+# the following: "SHA1 jdkCA"
+#
+# DenyAfterConstraint:
+# denyAfter YYYY-MM-DD
+# This constraint prohibits a certificate with the specified algorithm
+# from being used after the date regardless of the certificate's
+# validity. JAR files that are signed and timestamped before the
+# constraint date with certificates containing the disabled algorithm
+# will not be restricted. The date is processed in the UTC timezone.
+# This constraint can only be used once in a DisabledAlgorithm
+# expression.
+# Example: To deny usage of RSA 2048 bit certificates after Feb 3 2020,
+# use the following: "RSA keySize == 2048 & denyAfter 2020-02-03"
+#
+# UsageConstraint:
+# usage [TLSServer] [TLSClient] [SignedJAR]
+# This constraint prohibits the specified algorithm for
+# a specified usage. This should be used when disabling an algorithm
+# for all usages is not practical. 'TLSServer' restricts the algorithm
+# in TLS server certificate chains when server authentication is
+# performed. 'TLSClient' restricts the algorithm in TLS client
+# certificate chains when client authentication is performed.
+# 'SignedJAR' constrains use of certificates in signed jar files.
+# The usage type follows the keyword and more than one usage type can
+# be specified with a whitespace delimiter.
+# Example: "SHA1 usage TLSServer TLSClient"
+#
+# When an algorithm must satisfy more than one constraint, it must be
+# delimited by an ampersand '&'. For example, to restrict certificates in a
+# chain that terminate at a distribution provided trust anchor and contain
+# RSA keys that are less than or equal to 1024 bits, add the following
+# constraint: "RSA keySize <= 1024 & jdkCA".
+#
+# All DisabledAlgorithms expressions are processed in the order defined in the
+# property. This requires lower keysize constraints to be specified
+# before larger keysize constraints of the same algorithm. For example:
+# "RSA keySize < 1024 & jdkCA, RSA keySize < 2048".
+#
+# Note: The algorithm restrictions do not apply to trust anchors or
+# self-signed certificates.
+#
+# Note: This property is currently used by Oracle's PKIX implementation. It
+# is not guaranteed to be examined and used by other implementations.
+#
+# Example:
+# jdk.certpath.disabledAlgorithms=MD2, DSA, RSA keySize < 2048
+#
+#
+jdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer, \
+ RSA keySize < 1024, DSA keySize < 1024, EC keySize < 224, \
+ include jdk.disabled.namedCurves
+
+#
+# Legacy algorithms for certification path (CertPath) processing and
+# signed JAR files.
+#
+# In some environments, a certain algorithm or key length may be undesirable
+# but is not yet disabled.
+#
+# Tools such as keytool and jarsigner may emit warnings when these legacy
+# algorithms are used. See the man pages for those tools for more information.
+#
+# The syntax is the same as the "jdk.certpath.disabledAlgorithms" and
+# "jdk.jar.disabledAlgorithms" security properties.
+#
+# Note: This property is currently used by the JDK Reference
+# implementation. It is not guaranteed to be examined and used by other
+# implementations.
+
+jdk.security.legacyAlgorithms=SHA1, \
+ RSA keySize < 2048, DSA keySize < 2048
+
+#
+# Algorithm restrictions for signed JAR files
+#
+# In some environments, certain algorithms or key lengths may be undesirable
+# for signed JAR validation. For example, "MD2" is generally no longer
+# considered to be a secure hash algorithm. This section describes the
+# mechanism for disabling algorithms based on algorithm name and/or key length.
+# JARs signed with any of the disabled algorithms or key sizes will be treated
+# as unsigned.
+#
+# The syntax of the disabled algorithm string is described as follows:
+# DisabledAlgorithms:
+# " DisabledAlgorithm { , DisabledAlgorithm } "
+#
+# DisabledAlgorithm:
+# AlgorithmName [Constraint] { '&' Constraint }
+#
+# AlgorithmName:
+# (see below)
+#
+# Constraint:
+# KeySizeConstraint | DenyAfterConstraint
+#
+# KeySizeConstraint:
+# keySize Operator KeyLength
+#
+# DenyAfterConstraint:
+# denyAfter YYYY-MM-DD
+#
+# Operator:
+# <= | < | == | != | >= | >
+#
+# KeyLength:
+# Integer value of the algorithm's key length in bits
+#
+# Note: This property is currently used by the JDK Reference
+# implementation. It is not guaranteed to be examined and used by other
+# implementations.
+#
+# See "jdk.certpath.disabledAlgorithms" for syntax descriptions.
+#
+jdk.jar.disabledAlgorithms=MD2, MD5, RSA keySize < 1024, \
+ DSA keySize < 1024, include jdk.disabled.namedCurves
+
+#
+# Algorithm restrictions for Secure Socket Layer/Transport Layer Security
+# (SSL/TLS) processing
+#
+# In some environments, certain algorithms or key lengths may be undesirable
+# when using SSL/TLS. This section describes the mechanism for disabling
+# algorithms during SSL/TLS security parameters negotiation, including
+# protocol version negotiation, cipher suites selection, peer authentication
+# and key exchange mechanisms.
+#
+# Disabled algorithms will not be negotiated for SSL/TLS connections, even
+# if they are enabled explicitly in an application.
+#
+# For PKI-based peer authentication and key exchange mechanisms, this list
+# of disabled algorithms will also be checked during certification path
+# building and validation, including algorithms used in certificates, as
+# well as revocation information such as CRLs and signed OCSP Responses.
+# This is in addition to the jdk.certpath.disabledAlgorithms property above.
+#
+# See the specification of "jdk.certpath.disabledAlgorithms" for the
+# syntax of the disabled algorithm string.
+#
+# Note: The algorithm restrictions do not apply to trust anchors or
+# self-signed certificates.
+#
+# Note: This property is currently used by the JDK Reference implementation.
+# It is not guaranteed to be examined and used by other implementations.
+#
+# Example:
+# jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048
+jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
+ DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
+ include jdk.disabled.namedCurves
+
+# Legacy algorithms for Secure Socket Layer/Transport Layer Security (SSL/TLS)
+# processing in JSSE implementation.
+#
+# In some environments, a certain algorithm may be undesirable but it
+# cannot be disabled because of its use in legacy applications. Legacy
+# algorithms may still be supported, but applications should not use them
+# as the security strength of legacy algorithms are usually not strong enough
+# in practice.
+#
+# During SSL/TLS security parameters negotiation, legacy algorithms will
+# not be negotiated unless there are no other candidates.
+#
+# The syntax of the legacy algorithms string is described as this Java
+# BNF-style:
+# LegacyAlgorithms:
+# " LegacyAlgorithm { , LegacyAlgorithm } "
+#
+# LegacyAlgorithm:
+# AlgorithmName (standard JSSE algorithm name)
+#
+# See the specification of security property "jdk.certpath.disabledAlgorithms"
+# for the syntax and description of the "AlgorithmName" notation.
+#
+# Per SSL/TLS specifications, cipher suites have the form:
+# SSL_KeyExchangeAlg_WITH_CipherAlg_MacAlg
+# or
+# TLS_KeyExchangeAlg_WITH_CipherAlg_MacAlg
+#
+# For example, the cipher suite TLS_RSA_WITH_AES_128_CBC_SHA uses RSA as the
+# key exchange algorithm, AES_128_CBC (128 bits AES cipher algorithm in CBC
+# mode) as the cipher (encryption) algorithm, and SHA-1 as the message digest
+# algorithm for HMAC.
+#
+# The LegacyAlgorithm can be one of the following standard algorithm names:
+# 1. JSSE cipher suite name, e.g., TLS_RSA_WITH_AES_128_CBC_SHA
+# 2. JSSE key exchange algorithm name, e.g., RSA
+# 3. JSSE cipher (encryption) algorithm name, e.g., AES_128_CBC
+# 4. JSSE message digest algorithm name, e.g., SHA
+#
+# See SSL/TLS specifications and "Java Cryptography Architecture Standard
+# Algorithm Name Documentation" for information about the algorithm names.
+#
+# Note: This property is currently used by the JDK Reference implementation.
+# It is not guaranteed to be examined and used by other implementations.
+# There is no guarantee the property will continue to exist or be of the
+# same syntax in future releases.
+#
+# Example:
+# jdk.tls.legacyAlgorithms=DH_anon, DES_CBC, SSL_RSA_WITH_RC4_128_MD5
+#
+jdk.tls.legacyAlgorithms= \
+ K_NULL, C_NULL, M_NULL, \
+ DH_anon, ECDH_anon, \
+ RC4_128, RC4_40, DES_CBC, DES40_CBC, \
+ 3DES_EDE_CBC
+
+# The pre-defined default finite field Diffie-Hellman ephemeral (DHE)
+# parameters for Transport Layer Security (SSL/TLS/DTLS) processing.
+#
+# In traditional SSL/TLS/DTLS connections where finite field DHE parameters
+# negotiation mechanism is not used, the server offers the client group
+# parameters, base generator g and prime modulus p, for DHE key exchange.
+# It is recommended to use dynamic group parameters. This property defines
+# a mechanism that allows you to specify custom group parameters.
+#
+# The syntax of this property string is described as this Java BNF-style:
+# DefaultDHEParameters:
+# DefinedDHEParameters { , DefinedDHEParameters }
+#
+# DefinedDHEParameters:
+# "{" DHEPrimeModulus , DHEBaseGenerator "}"
+#
+# DHEPrimeModulus:
+# HexadecimalDigits
+#
+# DHEBaseGenerator:
+# HexadecimalDigits
+#
+# HexadecimalDigits:
+# HexadecimalDigit { HexadecimalDigit }
+#
+# HexadecimalDigit: one of
+# 0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f
+#
+# Whitespace characters are ignored.
+#
+# The "DefinedDHEParameters" defines the custom group parameters, prime
+# modulus p and base generator g, for a particular size of prime modulus p.
+# The "DHEPrimeModulus" defines the hexadecimal prime modulus p, and the
+# "DHEBaseGenerator" defines the hexadecimal base generator g of a group
+# parameter. It is recommended to use safe primes for the custom group
+# parameters.
+#
+# If this property is not defined or the value is empty, the underlying JSSE
+# provider's default group parameter is used for each connection.
+#
+# If the property value does not follow the grammar, or a particular group
+# parameter is not valid, the connection will fall back and use the
+# underlying JSSE provider's default group parameter.
+#
+# Note: This property is currently used by OpenJDK's JSSE implementation. It
+# is not guaranteed to be examined and used by other implementations.
+#
+# Example:
+# jdk.tls.server.defaultDHEParameters=
+# { \
+# FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 \
+# 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD \
+# EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245 \
+# E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED \
+# EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE65381 \
+# FFFFFFFF FFFFFFFF, 2}
+
+#
+# TLS key limits on symmetric cryptographic algorithms
+#
+# This security property sets limits on algorithms key usage in TLS 1.3.
+# When the amount of data encrypted exceeds the algorithm value listed below,
+# a KeyUpdate message will trigger a key change. This is for symmetric ciphers
+# with TLS 1.3 only.
+#
+# The syntax for the property is described below:
+# KeyLimits:
+# " KeyLimit { , KeyLimit } "
+#
+# WeakKeyLimit:
+# AlgorithmName Action Length
+#
+# AlgorithmName:
+# A full algorithm transformation.
+#
+# Action:
+# KeyUpdate
+#
+# Length:
+# The amount of encrypted data in a session before the Action occurs
+# This value may be an integer value in bytes, or as a power of two, 2^29.
+#
+# KeyUpdate:
+# The TLS 1.3 KeyUpdate handshake process begins when the Length amount
+# is fulfilled.
+#
+# Note: This property is currently used by OpenJDK's JSSE implementation. It
+# is not guaranteed to be examined and used by other implementations.
+#
+jdk.tls.keyLimits=AES/GCM/NoPadding KeyUpdate 2^37
+
+# Cryptographic Jurisdiction Policy defaults
+#
+# Import and export control rules on cryptographic software vary from
+# country to country. By default, the JDK provides two different sets of
+# cryptographic policy files:
+#
+# unlimited: These policy files contain no restrictions on cryptographic
+# strengths or algorithms.
+#
+# limited: These policy files contain more restricted cryptographic
+# strengths, and are still available if your country or
+# usage requires the traditional restrictive policy.
+#
+# The JDK JCE framework uses the unlimited policy files by default.
+# However the user may explicitly choose a set either by defining the
+# "crypto.policy" Security property or by installing valid JCE policy
+# jar files into the traditional JDK installation location. To better
+# support older JDK Update releases, the "crypto.policy" property is not
+# defined by default. See below for more information.
+#
+# The following logic determines which policy files are used:
+#
+# refers to the directory where the JRE was
+# installed and may be determined using the "java.home"
+# System property.
+#
+# 1. If the Security property "crypto.policy" has been defined,
+# then the following mechanism is used:
+#
+# The policy files are stored as jar files in subdirectories of
+# /lib/security/policy. Each directory contains a complete
+# set of policy files.
+#
+# The "crypto.policy" Security property controls the directory
+# selection, and thus the effective cryptographic policy.
+#
+# The default set of directories is:
+#
+# limited | unlimited
+#
+# 2. If the "crypto.policy" property is not set and the traditional
+# US_export_policy.jar and local_policy.jar files
+# (e.g. limited/unlimited) are found in the legacy
+# /lib/security directory, then the rules embedded within
+# those jar files will be used. This helps preserve compatibility
+# for users upgrading from an older installation.
+#
+# 3. If the jar files are not present in the legacy location
+# and the "crypto.policy" Security property is not defined,
+# then the JDK will use the unlimited settings (equivalent to
+# crypto.policy=unlimited)
+#
+# Please see the JCA documentation for additional information on these
+# files and formats.
+#
+# YOU ARE ADVISED TO CONSULT YOUR EXPORT/IMPORT CONTROL COUNSEL OR ATTORNEY
+# TO DETERMINE THE EXACT REQUIREMENTS.
+#
+# Please note that the JCE for Java SE, including the JCE framework,
+# cryptographic policy files, and standard JCE providers provided with
+# the Java SE, have been reviewed and approved for export as mass market
+# encryption item by the US Bureau of Industry and Security.
+#
+# Note: This property is currently used by the JDK Reference implementation.
+# It is not guaranteed to be examined and used by other implementations.
+#
+crypto.policy=unlimited
+
+#
+# The policy for the XML Signature secure validation mode. The mode is
+# enabled by setting the property "org.jcp.xml.dsig.secureValidation" to
+# true with the javax.xml.crypto.XMLCryptoContext.setProperty() method,
+# or by running the code with a SecurityManager.
+#
+# Policy:
+# Constraint {"," Constraint }
+# Constraint:
+# AlgConstraint | MaxTransformsConstraint | MaxReferencesConstraint |
+# ReferenceUriSchemeConstraint | KeySizeConstraint | OtherConstraint
+# AlgConstraint
+# "disallowAlg" Uri
+# MaxTransformsConstraint:
+# "maxTransforms" Integer
+# MaxReferencesConstraint:
+# "maxReferences" Integer
+# ReferenceUriSchemeConstraint:
+# "disallowReferenceUriSchemes" String { String }
+# KeySizeConstraint:
+# "minKeySize" KeyAlg Integer
+# OtherConstraint:
+# "noDuplicateIds" | "noRetrievalMethodLoops"
+#
+# For AlgConstraint, Uri is the algorithm URI String that is not allowed.
+# See the XML Signature Recommendation for more information on algorithm
+# URI Identifiers. For KeySizeConstraint, KeyAlg is the standard algorithm
+# name of the key type (ex: "RSA"). If the MaxTransformsConstraint,
+# MaxReferencesConstraint or KeySizeConstraint (for the same key type) is
+# specified more than once, only the last entry is enforced.
+#
+# Note: This property is currently used by the JDK Reference implementation. It
+# is not guaranteed to be examined and used by other implementations.
+#
+jdk.xml.dsig.secureValidationPolicy=\
+ disallowAlg http://www.w3.org/TR/1999/REC-xslt-19991116,\
+ disallowAlg http://www.w3.org/2001/04/xmldsig-more#rsa-md5,\
+ disallowAlg http://www.w3.org/2001/04/xmldsig-more#hmac-md5,\
+ disallowAlg http://www.w3.org/2001/04/xmldsig-more#md5,\
+ maxTransforms 5,\
+ maxReferences 30,\
+ disallowReferenceUriSchemes file http https,\
+ minKeySize RSA 1024,\
+ minKeySize DSA 1024,\
+ minKeySize EC 224,\
+ noDuplicateIds,\
+ noRetrievalMethodLoops
+
+#
+# Serialization process-wide filter
+#
+# A filter, if configured, is used by java.io.ObjectInputStream during
+# deserialization to check the contents of the stream.
+# A filter is configured as a sequence of patterns, each pattern is either
+# matched against the name of a class in the stream or defines a limit.
+# Patterns are separated by ";" (semicolon).
+# Whitespace is significant and is considered part of the pattern.
+#
+# If the system property jdk.serialFilter is also specified on the command
+# line, it supersedes the security property value defined here.
+#
+# If a pattern includes a "=", it sets a limit.
+# If a limit appears more than once the last value is used.
+# Limits are checked before classes regardless of the order in the sequence of patterns.
+# If any of the limits are exceeded, the filter status is REJECTED.
+#
+# maxdepth=value - the maximum depth of a graph
+# maxrefs=value - the maximum number of internal references
+# maxbytes=value - the maximum number of bytes in the input stream
+# maxarray=value - the maximum array length allowed
+#
+# Other patterns, from left to right, match the class or package name as
+# returned from Class.getName.
+# If the class is an array type, the class or package to be matched is the element type.
+# Arrays of any number of dimensions are treated the same as the element type.
+# For example, a pattern of "!example.Foo", rejects creation of any instance or
+# array of example.Foo.
+#
+# If the pattern starts with "!", the status is REJECTED if the remaining pattern
+# is matched; otherwise the status is ALLOWED if the pattern matches.
+# If the pattern ends with ".**" it matches any class in the package and all subpackages.
+# If the pattern ends with ".*" it matches any class in the package.
+# If the pattern ends with "*", it matches any class with the pattern as a prefix.
+# If the pattern is equal to the class name, it matches.
+# Otherwise, the status is UNDECIDED.
+#
+# Primitive types are not configurable with this filter.
+#
+#jdk.serialFilter=pattern;pattern
+
+#
+# RMI Registry Serial Filter
+#
+# The filter pattern uses the same format as jdk.serialFilter.
+# This filter can override the builtin filter if additional types need to be
+# allowed or rejected from the RMI Registry or to decrease limits but not
+# to increase limits.
+# If the limits (maxdepth, maxrefs, or maxbytes) are exceeded, the object is rejected.
+#
+# The maxdepth of any array passed to the RMI Registry is set to
+# 10000. The maximum depth of the graph is set to 20.
+# These limits can be reduced via the maxarray, maxdepth limits.
+#
+#sun.rmi.registry.registryFilter=pattern;pattern
+
+#
+# Array construction of any component type, including subarrays and arrays of
+# primitives, are allowed unless the length is greater than the maxarray limit.
+# The filter is applied to each array element.
+#
+# The built-in filter allows subclasses of allowed classes and
+# can approximately be represented as the pattern:
+#
+#sun.rmi.registry.registryFilter=\
+# maxarray=1000000;\
+# maxdepth=20;\
+# java.lang.String;\
+# java.lang.Number;\
+# java.lang.reflect.Proxy;\
+# java.rmi.Remote;\
+# sun.rmi.server.UnicastRef;\
+# sun.rmi.server.RMIClientSocketFactory;\
+# sun.rmi.server.RMIServerSocketFactory;\
+# java.rmi.activation.ActivationID;\
+# java.rmi.server.UID
+#
+# RMI Distributed Garbage Collector (DGC) Serial Filter
+#
+# The filter pattern uses the same format as jdk.serialFilter.
+# This filter can override the builtin filter if additional types need to be
+# allowed or rejected from the RMI DGC.
+#
+# The builtin DGC filter can approximately be represented as the filter pattern:
+#
+#sun.rmi.transport.dgcFilter=\
+# java.rmi.server.ObjID;\
+# java.rmi.server.UID;\
+# java.rmi.dgc.VMID;\
+# java.rmi.dgc.Lease;\
+# maxdepth=5;maxarray=10000
+
+# CORBA ORBIorTypeCheckRegistryFilter
+# Type check enhancement for ORB::string_to_object processing
+#
+# An IOR type check filter, if configured, is used by an ORB during
+# an ORB::string_to_object invocation to check the veracity of the type encoded
+# in the ior string.
+#
+# The filter pattern consists of a semi-colon separated list of class names.
+# The configured list contains the binary class names of the IDL interface types
+# corresponding to the IDL stub class to be instantiated.
+# As such, a filter specifies a list of IDL stub classes that will be
+# allowed by an ORB when an ORB::string_to_object is invoked.
+# It is used to specify a white list configuration of acceptable
+# IDL stub types which may be contained in a stringified IOR
+# parameter passed as input to an ORB::string_to_object method.
+#
+# Note: This property is currently used by the JDK Reference implementation.
+# It is not guaranteed to be examined and used by other implementations.
+#
+#com.sun.CORBA.ORBIorTypeCheckRegistryFilter=binary_class_name;binary_class_name
+
+#
+# JCEKS Encrypted Key Serial Filter
+#
+# This filter, if configured, is used by the JCEKS KeyStore during the
+# deserialization of the encrypted Key object stored inside a key entry.
+# If not configured or the filter result is UNDECIDED (i.e. none of the patterns
+# matches), the filter configured by jdk.serialFilter will be consulted.
+#
+# If the system property jceks.key.serialFilter is also specified, it supersedes
+# the security property value defined here.
+#
+# The filter pattern uses the same format as jdk.serialFilter. The default
+# pattern allows java.lang.Enum, java.security.KeyRep, java.security.KeyRep$Type,
+# and javax.crypto.spec.SecretKeySpec and rejects all the others.
+jceks.key.serialFilter = java.lang.Enum;java.security.KeyRep;\
+ java.security.KeyRep$Type;javax.crypto.spec.SecretKeySpec;!*
+
+#
+# PKCS12 KeyStore properties
+#
+# The following properties, if configured, are used by the PKCS12 KeyStore
+# implementation during the creation of a new keystore. Several of the
+# properties may also be used when modifying an existing keystore. The
+# properties can be overridden by a KeyStore API that specifies its own
+# algorithms and parameters.
+#
+# If an existing PKCS12 keystore is loaded and then stored, the algorithm and
+# parameter used to generate the existing Mac will be reused. If the existing
+# keystore does not have a Mac, no Mac will be created while storing. If there
+# is at least one certificate in the existing keystore, the algorithm and
+# parameters used to encrypt the last certificate in the existing keystore will
+# be reused to encrypt all certificates while storing. If the last certificate
+# in the existing keystore is not encrypted, all certificates will be stored
+# unencrypted. If there is no certificate in the existing keystore, any newly
+# added certificate will be encrypted (or stored unencrypted if algorithm
+# value is "NONE") using the "keystore.pkcs12.certProtectionAlgorithm" and
+# "keystore.pkcs12.certPbeIterationCount" values defined here. Existing private
+# and secret key(s) are not changed. Newly set private and secret key(s) will
+# be encrypted using the "keystore.pkcs12.keyProtectionAlgorithm" and
+# "keystore.pkcs12.keyPbeIterationCount" values defined here.
+#
+# In order to apply new algorithms and parameters to all entries in an
+# existing keystore, one can create a new keystore and add entries in the
+# existing keystore into the new keystore. This can be achieved by calling the
+# "keytool -importkeystore" command.
+#
+# If a system property of the same name is also specified, it supersedes the
+# security property value defined here.
+#
+# If the property is set to an illegal value,
+# an iteration count that is not a positive integer, or an unknown algorithm
+# name, an exception will be thrown when the property is used.
+# If the property is not set or empty, a default value will be used.
+#
+# Note: These properties are currently used by the JDK Reference implementation.
+# They are not guaranteed to be examined and used by other implementations.
+
+# The algorithm used to encrypt a certificate. This can be any non-Hmac PBE
+# algorithm defined in the Cipher section of the Java Security Standard
+# Algorithm Names Specification. When set to "NONE", the certificate
+# is not encrypted. The default value is "PBEWithSHA1AndRC2_40".
+#keystore.pkcs12.certProtectionAlgorithm = PBEWithSHA1AndRC2_40
+
+# The iteration count used by the PBE algorithm when encrypting a certificate.
+# This value must be a positive integer. The default value is 50000.
+#keystore.pkcs12.certPbeIterationCount = 50000
+
+# The algorithm used to encrypt a private key or secret key. This can be
+# any non-Hmac PBE algorithm defined in the Cipher section of the Java
+# Security Standard Algorithm Names Specification. The value must not be "NONE".
+# The default value is "PBEWithSHA1AndDESede".
+#keystore.pkcs12.keyProtectionAlgorithm = PBEWithSHA1AndDESede
+
+# The iteration count used by the PBE algorithm when encrypting a private key
+# or a secret key. This value must be a positive integer. The default value
+# is 50000.
+#keystore.pkcs12.keyPbeIterationCount = 50000
+
+# The algorithm used to calculate the optional MacData at the end of a PKCS12
+# file. This can be any HmacPBE algorithm defined in the Mac section of the
+# Java Security Standard Algorithm Names Specification. When set to "NONE",
+# no Mac is generated. The default value is "HmacPBESHA1".
+#keystore.pkcs12.macAlgorithm = HmacPBESHA1
+
+# The iteration count used by the MacData algorithm. This value must be a
+# positive integer. The default value is 100000.
+#keystore.pkcs12.macIterationCount = 100000
+
+# The iteration count used for password-based encryption (PBE) in JCEKS
+# keystores. Values in the range 10000 to 5000000 are considered valid.
+# If the value is out of this range, or is not a number, or is unspecified;
+# a default of 200000 is used.
+#
+# If the system property jdk.jceks.iterationCount is also specified, it
+# supersedes the security property value defined here.
+#
+#jdk.jceks.iterationCount = 200000
+
+#
+# Disabled mechanisms for the Simple Authentication and Security Layer (SASL)
+#
+# Disabled mechanisms will not be negotiated by both SASL clients and servers.
+# These mechanisms will be ignored if they are specified in the "mechanisms"
+# argument of "Sasl.createSaslClient" or the "mechanism" argument of
+# "Sasl.createSaslServer".
+#
+# The value of this property is a comma-separated list of SASL mechanisms.
+# The mechanisms are case-sensitive. Whitespaces around the commas are ignored.
+#
+# Note: This property is currently used by the JDK Reference implementation.
+# It is not guaranteed to be examined and used by other implementations.
+#
+# Example:
+# jdk.sasl.disabledMechanisms=PLAIN, CRAM-MD5, DIGEST-MD5
+jdk.sasl.disabledMechanisms=
+
+#
+# Policies for distrusting Certificate Authorities (CAs).
+#
+# This is a comma separated value of one or more case-sensitive strings, each
+# of which represents a policy for determining if a CA should be distrusted.
+# The supported values are:
+#
+#
+# SYMANTEC_TLS : Distrust TLS Server certificates anchored by a Symantec
+# root CA and issued after April 16, 2019 unless issued by one of the
+# following subordinate CAs which have a later distrust date:
+# 1. Apple IST CA 2 - G1, SHA-256 fingerprint:
+# AC2B922ECFD5E01711772FEA8ED372DE9D1E2245FCE3F57A9CDBEC77296A424B
+# Distrust after December 31, 2019.
+# 2. Apple IST CA 8 - G1, SHA-256 fingerprint:
+# A4FE7C7F15155F3F0AEF7AAA83CF6E06DEB97CA3F909DF920AC1490882D488ED
+# Distrust after December 31, 2019.
+# Leading and trailing whitespace surrounding each value are ignored.
+# Unknown values are ignored. If the property is commented out or set to the
+# empty String, no policies are enforced.
+#
+# Note: This property is currently used by the JDK Reference implementation.
+# It is not guaranteed to be supported by other SE implementations. Also, this
+# property does not override other security properties which can restrict
+# certificates such as jdk.tls.disabledAlgorithms or
+# jdk.certpath.disabledAlgorithms; those restrictions are still enforced even
+# if this property is not enabled.
+#
+jdk.security.caDistrustPolicies=SYMANTEC_TLS
+
+#
+# Policies for the proxy_impersonator Kerberos ccache configuration entry
+#
+# The proxy_impersonator ccache configuration entry indicates that the ccache
+# is a synthetic delegated credential for use with S4U2Proxy by an intermediate
+# server. The ccache file should also contain the TGT of this server and
+# an evidence ticket from the default principal of the ccache to this server.
+#
+# This security property determines how Java uses this configuration entry.
+# There are 3 possible values:
+#
+# no-impersonate - Ignore this configuration entry, and always act as
+# the owner of the TGT (if it exists).
+#
+# try-impersonate - Try impersonation when this configuration entry exists.
+# If no matching TGT or evidence ticket is found,
+# fallback to no-impersonate.
+#
+# always-impersonate - Always impersonate when this configuration entry exists.
+# If no matching TGT or evidence ticket is found,
+# no initial credential is read from the ccache.
+#
+# The default value is "always-impersonate".
+#
+# If a system property of the same name is also specified, it supersedes the
+# security property value defined here.
+#
+#jdk.security.krb5.default.initiate.credential=always-impersonate
+
+#
+# Trust Anchor Certificates - CA Basic Constraint check
+#
+# X.509 v3 certificates used as Trust Anchors (to validate signed code or TLS
+# connections) must have the cA Basic Constraint field set to 'true'. Also, if
+# they include a Key Usage extension, the keyCertSign bit must be set. These
+# checks, enabled by default, can be disabled for backward-compatibility
+# purposes with the jdk.security.allowNonCaAnchor System and Security
+# properties. In the case that both properties are simultaneously set, the
+# System value prevails. The default value of the property is "false".
+#
+#jdk.security.allowNonCaAnchor=true
+
+#
+# The default Character set name (java.nio.charset.Charset.forName())
+# for converting TLS ALPN values between byte arrays and Strings.
+# Prior versions of the JDK may use UTF-8 as the default charset. If
+# you experience interoperability issues, setting this property to UTF-8
+# may help.
+#
+# jdk.tls.alpnCharset=UTF-8
+jdk.tls.alpnCharset=ISO_8859_1
+
+#
+# JNDI Object Factories Filter
+#
+# This filter is used by the JNDI runtime to control the set of object factory classes
+# which will be allowed to instantiate objects from object references returned by
+# naming/directory systems. The factory class named by the reference instance will be
+# matched against this filter. The filter property supports pattern-based filter syntax
+# with the same format as jdk.serialFilter.
+#
+# Each pattern is matched against the factory class name to allow or disallow it's
+# instantiation. The access to a factory class is allowed unless the filter returns
+# REJECTED.
+#
+# Note: This property is currently used by the JDK Reference implementation.
+# It is not guaranteed to be examined and used by other implementations.
+#
+# If the system property jdk.jndi.object.factoriesFilter is also specified, it supersedes
+# the security property value defined here. The default value of the property is "*".
+#
+# The default pattern value allows any object factory class specified by the reference
+# instance to recreate the referenced object.
+#jdk.jndi.object.factoriesFilter=*
\ No newline at end of file
diff --git a/roles/fips/tasks/main.yml b/roles/fips/tasks/main.yml
new file mode 100644
index 0000000..80ac5ca
--- /dev/null
+++ b/roles/fips/tasks/main.yml
@@ -0,0 +1,127 @@
+---
+# tasks file for enabling fips on a RHEL-8 Server
+
+- name: Find java-11-openjdk directory
+ find:
+ paths: /usr/lib/jvm
+ patterns: "java-11-openjdk*"
+ file_type: "directory"
+ register: java_path
+
+- name: Get Java 11 directory path
+ set_fact:
+ java11_dir_path: "{{ java_path.files | map(attribute='path') | first }}"
+
+- name: Create directory for bouncy castle libraries
+ file:
+ path: "{{ java11_dir_path }}/lib/bc-fips/"
+ state: directory
+
+- name: Move bouncy castle libraries [1 of 2]
+ copy:
+ src: bc-fips-1.0.2.3.jar
+ dest: "{{ java11_dir_path }}/lib/bc-fips/bc-fips-1.0.2.3.jar"
+ owner: root
+ group: root
+ mode: '0644'
+
+- name: Move bouncy castle libraries [2 of 2]
+ copy:
+ src: bcpkix-fips-1.0.7.jar
+ dest: "{{ java11_dir_path }}/lib/bc-fips/bcpkix-fips-1.0.7.jar"
+ owner: root
+ group: root
+ mode: '0644'
+
+- name: Delete the custom JRE if it exists
+ file:
+ path: /usr/lib/jvm/java-fips
+ state: absent
+
+- name: Install jmods
+ package:
+ name: java-11-openjdk-jmods
+ state: present
+
+- name: Create custom fips JRE
+ shell: |
+ cd {{ java11_dir_path }}/bin
+ ./jlink --no-header-files --no-man-pages --compress=2 --module-path {{ java11_dir_path }}/lib/bc-fips --add-modules org.bouncycastle.fips.core,jdk.crypto.ec,"$({{ java11_dir_path }}/bin/java --list-modules | awk '{print $1}' | sed 's/@.*//' | paste -sd "," -)" --output /usr/lib/jvm/java-fips --ignore-signing-information
+ register: command_output
+
+- name: Relay custom FIPS JRE output
+ debug:
+ var: command_output.stdout_lines
+
+- name: Set fips java home directory
+ set_fact:
+ fips_java_home: /usr/lib/jvm/java-fips
+
+- name: Setup the fips providers in java.security
+ copy:
+ src: java.security
+ dest: /usr/lib/jvm/java-fips/conf/security
+ owner: root
+ group: root
+ mode: '0644'
+ backup: yes
+
+- name: Check for supported version
+ fail:
+ msg: "FIPS is currently only supported on RHEL-8, detected: {{ ansible_distribution }}"
+ when: ansible_os_family != 'RedHat' or ansible_distribution_major_version != "8"
+
+- name: Check if FIPS is enabled
+ shell: cat /proc/sys/crypto/fips_enabled
+ register: fips_enabled_output
+ changed_when: false
+
+- name: Set fact of FIPS enabled status
+ set_fact:
+ fips_sysctl_enabled: "{{ fips_enabled_output.stdout == '1' }}"
+
+- name: Report the FIPS status
+ debug:
+ msg: "fips_sysctl_enable : {{ fips_sysctl_enabled }} "
+
+- name: Report that we are enabling FIPS
+ debug:
+ msg: "Enabling FIPS because it has not been enabled yet"
+ when: not fips_sysctl_enabled
+
+- name: Report that we are skipping FIPS steps
+ debug:
+ msg: "Skipping FIPS setup because it is already enabled"
+ when: fips_sysctl_enabled
+
+- name: Install dracut-fips
+ yum:
+ name: dracut-fips
+ state: present
+ when: not fips_sysctl_enabled
+
+- name: Checking for AES-NI
+ raw: cat /proc/cpuinfo
+ register: cpu_contents
+ changed_when: cpu_contents.rc != 0
+
+- name: Installing dracut-fips-aesni if AES support is found
+ yum:
+ name: dracut-fips-aesni
+ state: present
+ when: cpu_contents.stdout.find('aes') != -1 and (not fips_sysctl_enabled)
+
+- name: Enabling FIPS
+ raw: fips-mode-setup --enable
+ register: fips_setup_output
+ when: not fips_sysctl_enabled
+
+- name: Report the fips-setup output
+ debug:
+ msg: "fips_setup: {{ fips_setup_output }}"
+ when: not fips_sysctl_enabled
+
+- name: Inform of need to reboot
+ debug:
+ msg: "WARNING: A system reboot is required to finalize enabling FIPS"
+ when: not fips_sysctl_enabled
\ No newline at end of file
diff --git a/roles/java/tasks/install-Debian.yml b/roles/java/tasks/install-Debian.yml
new file mode 100644
index 0000000..3bb53c7
--- /dev/null
+++ b/roles/java/tasks/install-Debian.yml
@@ -0,0 +1,21 @@
+---
+
+- name: install openjdk8
+ package:
+ name: openjdk-8-jdk
+ state: latest
+ when: not is_4p0_or_later
+
+- name: Add openjdk11 repo
+ command: add-apt-repository --yes ppa:openjdk-r/ppa
+ when: is_4p0_or_later
+
+- name: apt update
+ command: apt -y update
+ when: is_4p0_or_later
+
+- name: install openjdk11
+ package:
+ name: openjdk-11-jdk
+ state: latest
+ when: is_4p0_or_later
diff --git a/roles/java/tasks/install-RedHat.yml b/roles/java/tasks/install-RedHat.yml
new file mode 100644
index 0000000..a918759
--- /dev/null
+++ b/roles/java/tasks/install-RedHat.yml
@@ -0,0 +1,13 @@
+---
+
+- name: install openjdk-8
+ package:
+ name: java-1.8.0-openjdk-devel
+ state: latest
+ when: not is_4p0_or_later
+
+- name: install openjdk-11
+ package:
+ name: java-11-openjdk-devel
+ state: latest
+ when: is_4p0_or_later
\ No newline at end of file
diff --git a/roles/java/tasks/main.yml b/roles/java/tasks/main.yml
new file mode 100644
index 0000000..f49294a
--- /dev/null
+++ b/roles/java/tasks/main.yml
@@ -0,0 +1,57 @@
+---
+
+- name: Install Java
+ include_tasks: "install-{{ ansible_os_family }}.yml"
+
+- name: Find Java home directory
+ shell: readlink -f /usr/bin/javac | sed "s:/bin/javac::"
+ register: java_readlink
+
+- name: Set Java home directory
+ set_fact:
+ java_home: "{{ java_readlink.stdout }}"
+ when: java_home is undefined
+
+- name: Determine Java version
+ set_fact:
+ is_java_11: "{{ 'java-11' in java_home }}"
+
+- name: Check to see if we already have a copy of the archive in place
+ stat:
+ path: "/etc/fapolicyd/fapolicyd.rules"
+ register: fapolicy_enabled
+
+# Not strictly related to Java, but we need java_home to have a value before we can inject this block
+- name: "Configure fapolicyd to allow Content Controller"
+ blockinfile:
+ path: /etc/fapolicyd/fapolicyd.rules
+ state: present
+ insertbefore: '^# Only allow known ELF libs'
+ block: |
+ # Allow Ansible
+ allow perm=open exe=/usr/libexec/openssh/sftp-server trust=1 : path=/usr/lib/libz.so.1.2.11 ftype=application/x-sharedlib trust=0
+ allow perm=open exe=/usr/bin/sudo trust=1 : path=/usr/lib/libz.so.1.2.11 ftype=application/x-sharedlib trust=0
+ allow perm=open exe=/usr/libexec/platform-python3.6 trust=1 : path=/usr/lib/libz.so.1.2.11 ftype=application/x-sharedlib trust=0
+ allow perm=open exe=/usr/bin/dd trust=1 : all
+
+ # Allow zlib for MySQL
+ allow perm=open exe={{ java_home }}/jre/bin/java trust=1 : path=/usr/lib/libz.so.1.2.11 ftype=application/x-sharedlib trust=0
+ allow perm=open exe={{ java_home }}/bin/java trust=1 : path=/usr/lib/libz.so.1.2.11 ftype=application/x-sharedlib trust=0
+
+ # Allow Tomcat & Engine
+ allow perm=open exe={{ java_home }}/jre/bin/java trust=1 : path=/opt/apache-tomcat-{{ tomcat_version }}/bin/bootstrap.jar ftype=application/java-archive trust=0
+ allow perm=open exe={{ java_home }}/jre/bin/java trust=1 : path=/opt/apache-tomcat-{{ tomcat_version }}/bin/tomcat-juli.jar ftype=application/java-archive trust=0
+ allow perm=open exe={{ java_home }}/jre/bin/java trust=1 : dir=/opt/apache-tomcat-{{ tomcat_version }}/lib/ all trust=0
+ allow perm=open exe={{ java_home }}/jre/bin/java trust=1 : dir=/opt/apache-tomcat-{{ tomcat_version }}/webapps/ScormEngineInterface/WEB-INF/ all trust=0
+ allow perm=open exe={{ java_home }}/jre/bin/java trust=1 : dir=/opt/apache-tomcat-{{ tomcat_version }}/work/Catalina/localhost/ScormEngineInterface/ ftype=application/x-java-applet trust=0
+ allow perm=open exe={{ java_home }}/jre/bin/java trust=1 : dir=/opt/apache-tomcat-{{ tomcat_version }}/work/Catalina/localhost/ScormEngineInterface/ ftype=text/x-java trust=0
+
+ # Allow Content Controller files
+ allow perm=open exe={{ java_home }}/bin/java trust=1 : dir={{ cc_deploy_path }}/lib/ all trust=0
+ when: ansible_os_family == 'RedHat' and fapolicy_enabled.stat.exists
+
+- name: "Restart fapolicyd"
+ service:
+ name: "fapolicyd"
+ state: "restarted"
+ when: ansible_os_family == 'RedHat' and fapolicy_enabled.stat.exists
\ No newline at end of file
diff --git a/roles/local-content-proxy/defaults/main.yml b/roles/local-content-proxy/defaults/main.yml
new file mode 100644
index 0000000..796658b
--- /dev/null
+++ b/roles/local-content-proxy/defaults/main.yml
@@ -0,0 +1 @@
+content_proxy_release: 1.1.8
\ No newline at end of file
diff --git a/roles/local-content-proxy/handlers/main.yml b/roles/local-content-proxy/handlers/main.yml
new file mode 100644
index 0000000..8d5abe9
--- /dev/null
+++ b/roles/local-content-proxy/handlers/main.yml
@@ -0,0 +1,4 @@
+- name: restart content proxy
+ systemd:
+ service: content-proxy
+ state: restarted
diff --git a/roles/local-content-proxy/tasks/main.yml b/roles/local-content-proxy/tasks/main.yml
new file mode 100644
index 0000000..de24d7a
--- /dev/null
+++ b/roles/local-content-proxy/tasks/main.yml
@@ -0,0 +1,43 @@
+- name: make installation directory
+ file:
+ path: "{{ content_proxy_installation_dir }}"
+ state: directory
+ owner: root
+ group: root
+ mode: 0755
+
+- name: download content proxy release
+ aws_s3:
+ bucket: "rustici-release-content-proxy"
+ object: "content-proxy-{{ content_proxy_release }}-all.jar"
+ dest: "{{ content_proxy_installation_dir }}/content-proxy-{{ content_proxy_release }}.jar"
+ mode: get
+ aws_access_key: "{{ s3_download_key }}"
+ aws_secret_key: "{{ s3_download_secret }}"
+ region: us-east-1
+ notify: restart content proxy
+
+- name: install systemd service file
+ template:
+ src: content-proxy.service.j2
+ dest: /etc/systemd/system/content-proxy.service
+ owner: root
+ group: root
+ mode: 0644
+ notify: restart content proxy
+
+- name: install environment variable config file
+ template:
+ src: environment-variables.j2
+ dest: "{{ content_proxy_installation_dir }}/environment-variables"
+ owner: root
+ group: root
+ mode: 0600 # contains secrets
+ notify: restart content proxy
+
+- name: start, enable content proxy
+ systemd:
+ service: content-proxy
+ state: started
+ daemon_reload: yes
+ enabled: yes
diff --git a/roles/local-content-proxy/templates/content-proxy.service.j2 b/roles/local-content-proxy/templates/content-proxy.service.j2
new file mode 100644
index 0000000..6e75061
--- /dev/null
+++ b/roles/local-content-proxy/templates/content-proxy.service.j2
@@ -0,0 +1,14 @@
+[Unit]
+Description=Rustici Content Proxy
+Requires=network-online.target
+After=network-online.target
+
+[Service]
+EnvironmentFile={{ content_proxy_installation_dir }}/environment-variables
+ExecStart=/usr/bin/java -jar "{{ content_proxy_installation_dir }}/content-proxy-{{ content_proxy_release }}.jar" -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory
+
+User=tomcat
+Group=tomcat
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/local-content-proxy/templates/environment-variables.j2 b/roles/local-content-proxy/templates/environment-variables.j2
new file mode 100644
index 0000000..f29690d
--- /dev/null
+++ b/roles/local-content-proxy/templates/environment-variables.j2
@@ -0,0 +1,17 @@
+{% if not enable_contentvault %}
+CONTENT_PROXY_S3_STRIP_PREFIX=""
+{% endif %}
+
+CONTENT_PROXY_LISTEN_PORT="8090"
+
+CONTENT_AUTH_STRATEGY="{{ 'CONTENTVAULT' if enable_contentvault else 'CLOUDFRONT' }}"
+CC_SERVER_ADDRESS="{{ ServerName }}"
+
+{% if S3FileStorageEnabled %}
+AWS_ACCESS_KEY_ID="{{ S3FileStorageAwsId }}"
+AWS_SECRET_ACCESS_KEY="{{ S3FileStorageAwsKey }}"
+CONTENT_PROXY_S3_BUCKET_NAME="{{ S3FileStorageBucket }}"
+CONTENT_PROXY_CLOUDFRONT_PUBLIC_KEY_PATH="{{ cc_deploy_path }}/bin/{{ cloudfront_public_key }}"
+{% else %}
+CONTENT_PROXY_LOCAL_FILE_STORE="{{ content_root }}/"
+{% endif %}
diff --git a/roles/local-content-proxy/vars/main.yml b/roles/local-content-proxy/vars/main.yml
new file mode 100644
index 0000000..0af1dc3
--- /dev/null
+++ b/roles/local-content-proxy/vars/main.yml
@@ -0,0 +1 @@
+content_proxy_installation_dir: /opt/content-proxy
diff --git a/roles/mnt/defaults/main.yml b/roles/mnt/defaults/main.yml
new file mode 100644
index 0000000..b670484
--- /dev/null
+++ b/roles/mnt/defaults/main.yml
@@ -0,0 +1,7 @@
+---
+
+# The root directory where you want content to be stored.
+# For example, if you use /mnt, your content will be stored in /mnt/content
+# No trailing slash please.
+data_root: /mnt
+
diff --git a/roles/mnt/tasks/main.yml b/roles/mnt/tasks/main.yml
new file mode 100644
index 0000000..91dca73
--- /dev/null
+++ b/roles/mnt/tasks/main.yml
@@ -0,0 +1,52 @@
+---
+ - name: "Load {{ ansible_os_family }} variables"
+ include_vars: "{{ ansible_os_family }}.yml"
+
+ # We have to ensure that the tomcat group exists before we mount stuff
+ - name: add group "{{ apache_group }}"
+ group: name={{ apache_group }}
+
+ - name: add user "tomcat"
+ user:
+ name: tomcat
+ group: tomcat
+ home: /usr/share/tomcat
+ create_home: no
+ shell: /usr/sbin/nologin
+ become: true
+
+ - name: Create directories as needed for tomcat
+ file: path={{ item }} state=directory mode=2750 owner=tomcat group={{ apache_group }}
+ with_items:
+ - "{{ data_root }}"
+ - "{{ content_root }}"
+ - "{{ content_root }}/uploads"
+ - "{{ content_root }}/courses"
+ - "{{ content_root }}/courses/assets"
+ - "{{ content_root }}/courses/assets/accounts"
+ - "{{ content_root }}/xapi"
+ - "{{ content_root }}/zips"
+ - "{{ data_root }}/exports"
+ - "{{ data_root }}/s3temp"
+ - "{{ data_root }}/tmp"
+ - "{{ data_root }}/reports"
+
+ - name: Configure SELinux to allow Apache to access folders (RedHat)
+ sefcontext:
+ target: "{{ item }}(/.*)?"
+ setype: httpd_sys_rw_content_t
+ state: present
+ reload: true
+ with_items:
+ - "{{ content_root }}"
+ - "{{ data_root }}/exports"
+ - "{{ data_root }}/s3temp"
+ - "{{ data_root }}/tmp"
+ - "{{ data_root }}/reports"
+ when: ansible_os_family == 'RedHat'
+ ignore_errors: true
+
+ - name: Update SELinux security contexts (RedHat)
+ command: "restorecon -Rv {{ data_root }}"
+ when: ansible_os_family == 'RedHat'
+ ignore_errors: true
diff --git a/roles/mnt/vars/Debian.yml b/roles/mnt/vars/Debian.yml
new file mode 100644
index 0000000..2507817
--- /dev/null
+++ b/roles/mnt/vars/Debian.yml
@@ -0,0 +1,2 @@
+---
+apache_group: www-data
diff --git a/roles/mnt/vars/RedHat.yml b/roles/mnt/vars/RedHat.yml
new file mode 100644
index 0000000..0f948ea
--- /dev/null
+++ b/roles/mnt/vars/RedHat.yml
@@ -0,0 +1,2 @@
+---
+apache_group: apache
diff --git a/roles/mysql-config/defaults/main.yml b/roles/mysql-config/defaults/main.yml
new file mode 100644
index 0000000..32c920a
--- /dev/null
+++ b/roles/mysql-config/defaults/main.yml
@@ -0,0 +1,10 @@
+---
+
+# If this is set to false, then the mysql-config role won't try to create 'user'@'%' users.
+# This is primarily a workaround for certain implementations of RDS that get squirrely when we
+# try to do this, or for folks that are hand provisioning their SQL users
+
+# When in doubt, just leave it set to true and you'll be fine.
+allhostsmysql: true
+
+run_db_tasks_once: true
\ No newline at end of file
diff --git a/roles/mysql-config/handlers/main.yml b/roles/mysql-config/handlers/main.yml
new file mode 100644
index 0000000..2ca2623
--- /dev/null
+++ b/roles/mysql-config/handlers/main.yml
@@ -0,0 +1,7 @@
+
+ - name: restart mysql
+ action: service name="{{mysql_daemon}}" state=restarted
+
+ - name: flush privs
+ command: mysql -u "{{ mysql_root_user }}" -h "{{ cc_db_host }}" -p"{{ mysql_root_password}}" -e 'FLUSH PRIVILEGES;'
+
diff --git a/roles/mysql-config/tasks/main.yml b/roles/mysql-config/tasks/main.yml
new file mode 100644
index 0000000..0858e4e
--- /dev/null
+++ b/roles/mysql-config/tasks/main.yml
@@ -0,0 +1,85 @@
+---
+ - name: Install MySQL client
+ package:
+ name: "mysql{{ (mysql_version == '5.7') | ternary('-community-client', '') }}"
+ state: "{{ 'present' if fips_support_enabled else 'latest' }}"
+ update_cache: yes
+ when: ansible_os_family == 'RedHat'
+
+ - name: copy .my.cnf file with root password credentials
+ template: src=my.cnf.j2 dest=/root/.my.cnf owner=root mode=0600
+
+ # 'localhost' needs to be the last item for idempotency, see
+ # http://ansible.cc/docs/modules.html#mysql-user
+ # We ONLY set this when running this routine against localhost!
+ - name: update mysql root password for all root accounts
+ mysql_user: login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} name=root host={{ item }} password={{ mysql_root_password }} priv=*.*:ALL,GRANT check_implicit_admin=yes
+ with_items:
+ - "{{ ansible_hostname }}"
+ - 127.0.0.1
+ - ::1
+ - localhost
+ when: cc_db_host == "localhost"
+ notify: flush privs
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: delete anonymous MySQL server user for $server_hostname
+ action: mysql_user login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} user="" host="{{ ansible_hostname }}" state="absent"
+ when: cc_db_host == "localhost"
+ notify: flush privs
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: delete anonymous MySQL server user for localhost
+ action: mysql_user login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} user="" state="absent"
+ when: cc_db_host == "localhost"
+ notify: flush privs
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: remove the MySQL test database
+ action: mysql_db login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} db=test state=absent
+ when: cc_db_host == "localhost"
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: create the contentcontroller database
+ action: mysql_db login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} db={{ cc_db_name }} state="present" encoding='utf8' collation='utf8_general_ci'
+ when: cc_db_name is defined
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: create the scormengine database
+ action: mysql_db login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} db={{ engine_db_name }} state="present" encoding='utf8' collation='utf8_general_ci'
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: create contentcontroller MySQL users
+ mysql_user: name="{{ cc_db_username }}" login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} password="{{ cc_db_password }}" priv={{ cc_db_name }}.*:ALL state=present
+ notify: flush privs
+ when: cc_db_username is defined
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: create contentcontroller MySQL users
+ mysql_user: name="{{ cc_db_username }}" host="%" login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} password="{{ cc_db_password }}" priv={{ cc_db_name }}.*:ALL state=present
+ notify: flush privs
+ when: ( allhostsmysql is defined and allhostsmysql|bool == True ) and cc_db_username is defined
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: explicitly give CREATE TEMPORARY TABLE permissions to the cc_db_username user
+ mysql_user: name="{{ cc_db_username }}" host="%" login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} append_privs=true priv="{{ cc_db_name }}.*:CREATE TEMPORARY TABLES" state=present
+ notify: flush privs
+ when: cc_db_username is defined
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: explicitly give trigger permissions to the contentcontroller user (for RDS compatibility)
+ mysql_user: name="{{ cc_db_username }}" host="%" login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} append_privs=true priv={{ cc_db_name }}.*:TRIGGER state=present
+ notify: flush privs
+ when: cc_db_username is defined
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: create ScormEngine MySQL users
+ mysql_user: name="{{ engine_db_username }}" login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} password="{{ engine_db_password }}" priv={{ engine_db_name }}.*:ALL state=present
+ notify: flush privs
+ run_once: "{{ run_db_tasks_once }}"
+
+ - name: create ScormEngine MySQL users
+ mysql_user: name="{{ engine_db_username }}" host="%" login_user="{{ mysql_root_user }}" login_password={{ mysql_root_password }} login_host={{ cc_db_host }} password="{{ engine_db_password }}" priv={{ engine_db_name }}.*:ALL state=present
+ notify: flush privs
+ when: allhostsmysql is defined and allhostsmysql|bool == True
+ run_once: "{{ run_db_tasks_once }}"
diff --git a/roles/mysql-config/templates/my.cnf.j2 b/roles/mysql-config/templates/my.cnf.j2
new file mode 100644
index 0000000..9ab8201
--- /dev/null
+++ b/roles/mysql-config/templates/my.cnf.j2
@@ -0,0 +1,4 @@
+[client]
+host={{ cc_db_host }}
+user={{ mysql_root_user }}
+password="{{ mysql_root_password }}"
diff --git a/roles/mysql-local/defaults/main.yml b/roles/mysql-local/defaults/main.yml
new file mode 100644
index 0000000..bbf42cc
--- /dev/null
+++ b/roles/mysql-local/defaults/main.yml
@@ -0,0 +1,3 @@
+---
+
+initialize_mysql: true
\ No newline at end of file
diff --git a/roles/mysql-local/handlers/main.yml b/roles/mysql-local/handlers/main.yml
new file mode 100644
index 0000000..0fbed4a
--- /dev/null
+++ b/roles/mysql-local/handlers/main.yml
@@ -0,0 +1,6 @@
+
+ - name: restart mysql
+ action: service name="{{mysql_daemon}}" state=restarted
+
+ - name: start mysql
+ action: service name="{{mysql_daemon}}" state=started
diff --git a/roles/mysql-local/tasks/install-Debian.yml b/roles/mysql-local/tasks/install-Debian.yml
new file mode 100644
index 0000000..755dead
--- /dev/null
+++ b/roles/mysql-local/tasks/install-Debian.yml
@@ -0,0 +1,46 @@
+---
+- name: "Install server and client with dependencies"
+ apt:
+ name: "mysql-server"
+ state: present
+ dpkg_options: 'force-confold,force-confdef'
+ notify: restart mysql
+ when: not (ansible_distribution_major_version == "20" and mysql_version == '5.7')
+
+- name: "Install MySQL (Ubuntu 20 with MySQL 5.7)"
+ shell: |
+ wget https://dev.mysql.com/get/mysql-apt-config_0.8.12-1_all.deb
+ DEBIAN_FRONTEND=noninteractive dpkg -i mysql-apt-config_0.8.12-1_all.deb
+ apt update
+ DEBIAN_FRONTEND=noninteractive apt install -fy mysql-client=5.7* mysql-community-server=5.7* mysql-server=5.7*
+ when: ansible_distribution_major_version == "20" and mysql_version == '5.7'
+
+- name: Install other packages
+ package: name={{ item }} state=present
+ with_items:
+ - libmariadb-java
+
+- name: Bind MySQL server to all interfaces
+ lineinfile: dest=/etc/mysql/mysql.conf.d/mysqld.cnf regexp="^bind-address" line='bind-address = 0.0.0.0'
+ notify: restart mysql
+
+- name: Set MySQL server variables
+ lineinfile: dest=/etc/mysql/mysql.conf.d/mysqld.cnf regexp="^{{ item.property }}" line='{{ item.property }} = {{ item.value }}'
+ notify: restart mysql
+ with_items:
+ - { property: "sql_mode", value: "NO_ENGINE_SUBSTITUTION" }
+ - { property: "default_authentication_plugin", value: "mysql_native_password" }
+ - { property: "log_bin_trust_function_creators", value: "1" }
+ register: mysql_variables
+
+- name: Start MySQL Server
+ service:
+ name: "{{ mysql_daemon }}"
+ # To avoid restarting the service unnecessarily, we should only restart
+ # MySQL if the previous step actually changed something.
+ state: "{{ mysql_variables.changed | ternary('restarted', 'started') }}"
+ enabled: true
+
+- name: Run MySQL upgrade tool
+ shell: mysql_upgrade
+ ignore_errors: true
diff --git a/roles/mysql-local/tasks/install-RedHat.yml b/roles/mysql-local/tasks/install-RedHat.yml
new file mode 100644
index 0000000..2189586
--- /dev/null
+++ b/roles/mysql-local/tasks/install-RedHat.yml
@@ -0,0 +1,124 @@
+---
+- name: Install MySQL Server
+ yum:
+ name: "{{ (mysql_version == '8.0') | ternary('mysql-server', 'mysql-community-server') }}"
+ state: latest
+ register: mysql_redhat_yum_results
+
+- name: Update MySQL config file
+ replace:
+ path: "/etc/my.cnf"
+ regexp: '\[client-server\]'
+ replace: '[mysqld]'
+
+- name: Set default MySQL properties
+ lineinfile: dest=/etc/my.cnf regexp="^{{ item.property }}" line='{{ item.property }} = {{ item.value }}'
+ notify: restart mysql
+ with_items:
+ - { property: "sql_mode", value: "\"NO_ENGINE_SUBSTITUTION\"" }
+ - { property: "default-authentication-plugin", value: "\"mysql_native_password\"" }
+ - { property: "log_bin_trust_function_creators", value: "1" }
+
+- name: Determine zlib version
+ block:
+ - name: Check existing library
+ raw: ls /usr/lib/libz.so.*.*
+ register: zlib_version
+
+ - name: Recording existing version
+ set_fact:
+ upgrade_zlib: zlib_version.stdout is version('/usr/lib/libz.so.1.2.11', '<')
+ rescue:
+ - name: Recording zlib absence
+ set_fact:
+ upgrade_zlib: true
+
+- name: Update zlib
+ when: upgrade_zlib|bool
+ block:
+ - name: Download zlib-1.2.11
+ get_url:
+ url: https://www.zlib.net/fossils/zlib-1.2.11.tar.gz
+ dest: /tmp
+
+ - name: Extract zlib-1.2.11
+ unarchive:
+ remote_src: yes
+ src: /tmp/zlib-1.2.11.tar.gz
+ dest: /tmp/
+
+ # ./configure saves a log file to ./, so we have to cd into that
+ # directory before running the command
+ - name: Configure zlib-1.2.11
+ raw: (cd /tmp/zlib-1.2.11/ && ./configure --shared --prefix=/usr)
+
+ - name: Build zlib-1.2.11
+ make:
+ chdir: /tmp/zlib-1.2.11/
+
+ - name: Install zlib-1.2.11
+ make:
+ chdir: /tmp/zlib-1.2.11/
+ target: install
+
+ - name: Clean up after zlib installation
+ file:
+ path: /tmp/zlib-1.2.11
+ state: absent
+
+- name: Start MySQL Server
+ service:
+ name: "{{ mysql_daemon }}"
+ state: started
+ enabled: true
+
+- name: Check to see if .my.cnf has been written
+ stat: path=/root/.my.cnf
+ register: mysql_my_cnf_exists
+
+- name: Run MySQL upgrade tool
+ shell: mysql_upgrade
+ when: mysql_my_cnf_exists.stat.exists and mysql_redhat_yum_results is changed
+ ignore_errors: true
+
+- name: Get temporary MySQL password on initial install
+ shell: "cat /var/log/mysqld.log | grep 'A temporary password is generated for' | awk '{print $NF}'"
+ args:
+ executable: /bin/bash
+ register: mysql_redhat_initial_root_password_results
+ when: mysql_my_cnf_exists.stat.exists == false and mysql_redhat_yum_results is changed
+
+- name: Set fact for MySQL root password
+ set_fact:
+ mysql_redhat_initial_root_password: "{{ mysql_redhat_initial_root_password_results.stdout }}"
+ when: mysql_redhat_initial_root_password_results is changed
+
+- name: Set initial MySQL root password if necessary
+ shell: |
+ # Temp password provided from MySQL logs
+ TEMP_PASS='{{ mysql_redhat_initial_root_password }}'
+
+ # Unexpire root password
+ mysql --connect-expired-password -uroot -p"$TEMP_PASS" -Bse "ALTER USER root@localhost IDENTIFIED WITH mysql_native_password BY '$TEMP_PASS';"
+ mysql --connect-expired-password -uroot -p"$TEMP_PASS" -Bse "ALTER USER root@localhost PASSWORD EXPIRE NEVER;"
+ args:
+ executable: /bin/bash
+ when: (mysql_version == "5.7" or ansible_distribution_major_version == "7") and mysql_redhat_initial_root_password is defined
+
+- name: Remove unnecessary validation
+ shell: |
+ # Remove password validation plugin (since we don't have end users connecting to the DB, it isn't needed)
+ mysql -uroot -p"$TEMP_PASS" -Bse "UNINSTALL PLUGIN validate_password;"
+ args:
+ executable: /bin/bash
+ when: ansible_distribution_major_version == 7 and mysql_redhat_initial_root_password is defined
+
+- name: Copy .my.cnf file with root password credentials
+ template: src=root.my.cnf.j2 dest=/root/.my.cnf owner=root mode=0600
+ when: mysql_redhat_initial_root_password is defined
+
+- name: Restart MySQL Server
+ service:
+ name: "{{ mysql_daemon }}"
+ state: restarted
+ when: mysql_redhat_initial_root_password is defined
diff --git a/roles/mysql-local/tasks/main.yml b/roles/mysql-local/tasks/main.yml
new file mode 100644
index 0000000..8fa6f37
--- /dev/null
+++ b/roles/mysql-local/tasks/main.yml
@@ -0,0 +1,25 @@
+---
+- name: Include OS-specific variables
+ include_vars: "{{ ansible_os_family }}.yml"
+ when: cc_db_host == "localhost"
+
+- name: Install MySQL
+ include_tasks: "install-{{ ansible_os_family }}.yml"
+ when: cc_db_host == "localhost"
+
+- name: Determine MySQL socket
+ set_fact:
+ mysql_socket: "{{ (ansible_os_family == 'Debian') | ternary('/var/run/mysqld/mysqld.sock', '/var/lib/mysql/mysql.sock') }}"
+ when: cc_db_host == "localhost"
+
+- name: Set root account password
+ mysql_user: name=root password={{ mysql_root_password }} login_unix_socket={{ mysql_socket }} login_host=localhost priv=*.*:ALL,GRANT state=present
+ when: cc_db_host == "localhost"
+
+- name: copy .my.cnf file with root password credentials
+ template: src=my.cnf.j2 dest=/root/.my.cnf owner=root mode=0600
+ when: cc_db_host == "localhost"
+
+- name: Create superuser account if the specified account isn't root
+ mysql_user: login_user="root" login_password={{ mysql_root_password }} name={{ mysql_root_user }} password={{ mysql_root_password }} priv=*.*:ALL,GRANT state=present
+ when: mysql_root_user != "root" and cc_db_host == "localhost"
diff --git a/roles/mysql-local/templates/my.cnf.j2 b/roles/mysql-local/templates/my.cnf.j2
new file mode 100644
index 0000000..9ab8201
--- /dev/null
+++ b/roles/mysql-local/templates/my.cnf.j2
@@ -0,0 +1,4 @@
+[client]
+host={{ cc_db_host }}
+user={{ mysql_root_user }}
+password="{{ mysql_root_password }}"
diff --git a/roles/mysql-local/templates/root.my.cnf.j2 b/roles/mysql-local/templates/root.my.cnf.j2
new file mode 100644
index 0000000..1659f68
--- /dev/null
+++ b/roles/mysql-local/templates/root.my.cnf.j2
@@ -0,0 +1,4 @@
+[client]
+host={{ cc_db_host }}
+user=root
+password="{{ mysql_redhat_initial_root_password }}"
diff --git a/roles/mysql-local/vars/Debian.yml b/roles/mysql-local/vars/Debian.yml
new file mode 100644
index 0000000..96eab4e
--- /dev/null
+++ b/roles/mysql-local/vars/Debian.yml
@@ -0,0 +1,2 @@
+---
+mysql_daemon: mysql
diff --git a/roles/mysql-local/vars/RedHat.yml b/roles/mysql-local/vars/RedHat.yml
new file mode 100644
index 0000000..ccb4bcf
--- /dev/null
+++ b/roles/mysql-local/vars/RedHat.yml
@@ -0,0 +1,2 @@
+---
+mysql_daemon: mysqld
diff --git a/roles/newrelic/defaults/main.yml b/roles/newrelic/defaults/main.yml
new file mode 100644
index 0000000..cd21505
--- /dev/null
+++ b/roles/newrelic/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+
diff --git a/roles/newrelic/files/newrelic.jar b/roles/newrelic/files/newrelic.jar
new file mode 100644
index 0000000..a0f397d
Binary files /dev/null and b/roles/newrelic/files/newrelic.jar differ
diff --git a/roles/newrelic/handlers/main.yml b/roles/newrelic/handlers/main.yml
new file mode 100644
index 0000000..c7b7a66
--- /dev/null
+++ b/roles/newrelic/handlers/main.yml
@@ -0,0 +1,7 @@
+---
+
+ - name: restart newrelic-plugin-agent
+ service: name=newrelic-plugin-agent state=restarted
+
+ - name: restart newrelic-sysmond
+ service: name=newrelic-sysmond state=restarted
diff --git a/roles/newrelic/tasks/main.yml b/roles/newrelic/tasks/main.yml
new file mode 100644
index 0000000..df5def7
--- /dev/null
+++ b/roles/newrelic/tasks/main.yml
@@ -0,0 +1,40 @@
+---
+
+ - name: Create New Relic Directory
+ file: dest="{{ tomcat_base }}/newrelic" owner=tomcat group=tomcat state=directory mode=0755
+
+ - name: install new-relic-jar ( SCORM Engine )
+ copy: src=newrelic.jar dest="{{ tomcat_base }}/newrelic/newrelic.jar"
+
+ - name: install new-relic-jar ( Content Controller )
+ copy: src=newrelic.jar dest="{{ cc_deploy_path }}/newrelic.jar"
+ when: cc_deploy_path is defined
+
+ - name: install NewRelic.yml (SCORM Engine)
+ template: src=newrelic.engine.yml dest="{{ tomcat_base }}/newrelic/newrelic.yml"
+ notify:
+ - restart tomcat
+
+ - name: install NewRelic.yml (ContentController)
+ template: src=newrelic.cc.yml dest="{{ cc_deploy_path }}/newrelic.yml"
+ notify:
+ - restart contentcontroller
+ when: cc_deploy_path is defined
+
+ - name: Edit catalina.sh to enable APM agent in tomcat
+ lineinfile:
+ dest: "{{ tomcat_base }}/bin/catalina.sh"
+ line: 'export JAVA_OPTS="$JAVA_OPTS -javaagent:/usr/share/tomcat/newrelic/newrelic.jar"'
+ state: present
+ insertafter: ^JAVA_OPTS
+ notify: restart tomcat
+
+ - name: Edit contentcontroller-service script to enable APM
+ lineinfile:
+ dest: "{{ cc_deploy_path }}/bin/contentcontroller-service"
+ line: 'export JAVA_OPTS="$JAVA_OPTS -javaagent:/var/lib/contentcontroller/newrelic.jar"'
+ state: present
+ insertafter: ^DEFAULT_JVM_OPTS
+ when: cc_deploy_path is defined
+ notify: restart contentcontroller
+
diff --git a/roles/newrelic/templates/newrelic-hostname-setter.conf b/roles/newrelic/templates/newrelic-hostname-setter.conf
new file mode 100644
index 0000000..f0ac547
--- /dev/null
+++ b/roles/newrelic/templates/newrelic-hostname-setter.conf
@@ -0,0 +1,8 @@
+description "Adds the FQDN as the hostname in the New Relic sysmond config"
+
+# cloud-init sets up the hostname, so our service will run after it
+start on (started cloud-init)
+
+task
+exec /usr/local/bin/nrsysmond-hostname.sh
+
diff --git a/roles/newrelic/templates/newrelic-plugin-agent b/roles/newrelic/templates/newrelic-plugin-agent
new file mode 100644
index 0000000..87a90cd
--- /dev/null
+++ b/roles/newrelic/templates/newrelic-plugin-agent
@@ -0,0 +1,106 @@
+#! /bin/sh
+
+### BEGIN INIT INFO
+# Provides: newrelic-plugin-agent
+# Required-Start: $network $local_fs $remote_fs
+# Required-Stop: $remote_fs
+# Should-Start: $named
+# Should-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: The New Relic Plugin Agent
+# Description: The New Relic Plugin Agent
+### END INIT INFO
+
+NAME=newrelic-plugin-agent
+CONFIG=/etc/newrelic/newrelic-plugin-agent.cfg
+DAEMON=/usr/local/bin/newrelic-plugin-agent
+DAEMON_OPTS="-c $CONFIG"
+DESC="New Relic Plugin Agent"
+TIMEOUT=5
+PIDDIR_MODE=755
+PIDDIR_OWNER=
+PIDDIR_OWNER_FALLBACK="root"
+
+# Include newrelic plugin agent defaults if available
+if [ -f /etc/default/$NAME ] ; then
+ . /etc/default/$NAME
+fi
+
+# define LSB log_* functions.
+. /lib/lsb/init-functions
+
+check_daemon() {
+ if [ ! -x $DAEMON ]; then
+ log_action_msg "$DAEMON not found" || true
+ log_end_msg 1 || false
+ exit 1
+ fi
+}
+
+check_config() {
+ if [ ! -e $CONFIG ]; then
+ log_action_msg "Configuration file $CONFIG not found" || true
+ log_end_msg 1 || false
+ exit 1
+ fi
+}
+
+check_pid() {
+ PIDDIR=$(dirname $PIDFILE)
+ if [ ! id -u $PIDDIR_OWNER > /dev/null 2>&1 ]; then
+ PIDDIR_OWNER=$PIDDIR_OWNER_FALLBACK
+ fi
+ if [ ! -d $PIDDIR ]; then
+ install -m $PIDDIR_MODE -o $PIDDIR_OWNER -g $PIDDIR_OWNER -d $PIDDIR
+ log_action_msg "PID directory was not found and created" || true
+ fi;
+}
+
+PIDFILE=`sed -n -e 's/^[ ]*pidfile[ ]*:[ ]*//p' -e 's/[ ]*$//' $CONFIG`
+
+export PATH="${PATH}:/usr/sbin:/sbin/:usr/local/sbin:/usr/local/bin"
+
+case "$1" in
+ start)
+ check_daemon
+ check_config
+ check_pid
+
+ log_daemon_msg "Starting $DESC" "$NAME" || true
+ if [ -s $PIDFILE ] && kill -0 `cat $PIDFILE` > /dev/null 2>&1; then
+ log_action_msg "apparently already running" || true
+ log_end_msg 0 || true
+ exit 0
+ fi
+
+ if start-stop-daemon --oknodo --start --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_OPTS; then
+ log_end_msg 0 || true
+ else
+ log_end_msg 1 || false
+ fi
+ ;;
+ stop)
+ log_daemon_msg "Stopping $DESC" "$NAME" || true
+ check_daemon
+
+ if start-stop-daemon --oknodo --stop --retry $TIMEOUT --pidfile $PIDFILE; then
+ log_end_msg 0 || true
+ else
+ log_end_msg 1 || false
+ fi
+ ;;
+ status)
+ status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
+ ;;
+ restart|force-reload)
+ $0 stop
+ $0 start
+ ;;
+ *)
+ echo "Usage: /etc/init.d/$NAME {start|stop|restart|force-reload}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/roles/newrelic/templates/newrelic-plugin-agent-default b/roles/newrelic/templates/newrelic-plugin-agent-default
new file mode 100644
index 0000000..4a58778
--- /dev/null
+++ b/roles/newrelic/templates/newrelic-plugin-agent-default
@@ -0,0 +1 @@
+PIDDIR_OWNER=newrelic
diff --git a/roles/newrelic/templates/newrelic-plugin-agent.cfg b/roles/newrelic/templates/newrelic-plugin-agent.cfg
new file mode 100644
index 0000000..8878b54
--- /dev/null
+++ b/roles/newrelic/templates/newrelic-plugin-agent.cfg
@@ -0,0 +1,176 @@
+%YAML 1.2
+---
+Application:
+ license_key: {{ newrelic_license_key }}
+ wake_interval: 60
+ #newrelic_api_timeout: 10
+ #proxy: http://localhost:8080
+
+ apache_httpd:
+ scheme: http
+ host: localhost
+ # verify_ssl_cert: true
+ port: 80
+ path: /server-status
+
+ #couchdb:
+ # name: localhost
+ # host: localhost
+ # verify_ssl_cert: true
+ # port: 5984
+ # username: foo
+ # password: bar
+
+ #elasticsearch:
+ # name: Clustername
+ # host: localhost
+ # port: 9200
+ # scheme: http
+
+ #haproxy:
+ # name: hostname
+ # scheme: http
+ # host: localhost
+ # port: 80
+ # verify_ssl_cert: true
+ # path: /haproxy?stats;csv
+
+ #memcached:
+ # name: localhost
+ # host: localhost
+ # port: 11211
+ # path: /path/to/unix/socket
+
+ #mongodb:
+ # name: hostname
+ # host: localhost
+ # port: 27017
+ # admin_username: user
+ # admin_password: pass
+ # ssl: False
+ # ssl_keyfile: /path/to/keyfile
+ # ssl_certfile: /path/to/certfile
+ # ssl_cert_reqs: 0 # Should be 0 for ssl.CERT_NONE, 1 for ssl.CERT_OPTIONAL, 2 for ssl.CERT_REQUIRED
+ # ssl_ca_certs: /path/to/cacerts file
+ # databases:
+ # - test
+ # - yourdbname
+
+ #mongodb: # Use when authentication is required
+ # name: hostname
+ # host: localhost
+ # port: 27017
+ # admin_username: user
+ # admin_password: pass
+ # ssl: False
+ # ssl_keyfile: /path/to/keyfile
+ # ssl_certfile: /path/to/certfile
+ # ssl_cert_reqs: 0 # Should be 0 for ssl.CERT_NONE, 1 for ssl.CERT_OPTIONAL, 2 for ssl.CERT_REQUIRED
+ # ssl_ca_certs: /path/to/cacerts file
+ # databases:
+ # test:
+ # username: user
+ # password: pass
+ # yourdbname:
+ # username: user
+ # password: pass
+
+ #nginx:
+ # name: hostname
+ # scheme: http
+ # host: localhost
+ # port: 80
+ # verify_ssl_cert: true
+ # path: /nginx_stub_status
+
+ #pgbouncer:
+ # host: localhost
+ # port: 6000
+ # user: stats
+
+ #php_apc:
+ # name: hostname
+ # scheme: http
+ # host: localhost
+ # verify_ssl_cert: true
+ # port: 80
+ # path: /apc-nrp.php
+
+ #php_fpm:
+ # - name: fpm-pool
+ # scheme: https
+ # host: localhost
+ # port: 443
+ # path: /fpm_status
+ # query: json
+
+ #postgresql:
+ # host: localhost
+ # port: 5432
+ # user: postgres
+ # dbname: postgres
+ # superuser: False
+
+ #rabbitmq:
+ # name: rabbitmq@localhost
+ # host: localhost
+ # port: 15672
+ # verify_ssl_cert: true
+ # username: guest
+ # password: guest
+ # vhosts: # [OPTIONAL, track this vhosts' queues only]
+ # production_vhost:
+ # queues: [encode_video, ] # [OPTIONAL, track this queues only]
+ # staging_vhost: # [track every queue for this vhost]
+ #
+
+ #redis:
+ # - name: localhost
+ # host: localhost
+ # port: 6379
+ # db_count: 16
+ # password: foo # [OPTIONAL]
+ # #path: /var/run/redis/redis.sock
+ # - name: localhost
+ # host: localhost
+ # port: 6380
+ # db_count: 16
+ # password: foo # [OPTIONAL]
+ # #path: /var/run/redis/redis.sock
+
+ #riak:
+ # name: localhost
+ # host: node0.riak0.scs.mtmeprod.net
+ # verify_ssl_cert: true
+ # port: 8098
+
+ #uwsgi:
+ # name: localhost
+ # host: localhost
+ # port: 1717
+ # path: /path/to/unix/socket
+
+Daemon:
+ user: newrelic
+ pidfile: /var/run/newrelic/newrelic-plugin-agent.pid
+
+Logging:
+ formatters:
+ verbose:
+ format: '%(levelname) -10s %(asctime)s %(process)-6d %(processName) -15s %(threadName)-10s %(name) -45s %(funcName) -25s L%(lineno)-6d: %(message)s'
+ handlers:
+ file:
+ class : logging.handlers.RotatingFileHandler
+ formatter: verbose
+ filename: /var/log/newrelic/newrelic-plugin-agent.log
+ maxBytes: 10485760
+ backupCount: 3
+ loggers:
+ newrelic_plugin_agent:
+ level: INFO
+ propagate: True
+ handlers: [console, file]
+ requests:
+ level: ERROR
+ propagate: True
+ handlers: [console, file]
diff --git a/roles/newrelic/templates/newrelic.cc.yml b/roles/newrelic/templates/newrelic.cc.yml
new file mode 100644
index 0000000..becb446
--- /dev/null
+++ b/roles/newrelic/templates/newrelic.cc.yml
@@ -0,0 +1,303 @@
+# This file configures the New Relic Agent. New Relic monitors
+# Java applications with deep visibility and low overhead. For more details and additional
+# configuration options visit https://docs.newrelic.com/docs/java/java-agent-configuration.
+#
+# This configuration file is custom generated for Rustici Software, LLC
+#
+# This section is for settings common to all environments.
+# Do not add anything above this next line.
+common: &default_settings
+
+ # ============================== LICENSE KEY ===============================
+ # You must specify the license key associated with your New Relic
+ # account. For example, if your license key is 12345 use this:
+ # license_key: '12345'
+ # The key binds your Agent's data to your account in the New Relic service.
+ license_key: {{ newrelic_license_key }}
+
+ # Agent Enabled
+ # Use this setting to disable the agent instead of removing it from the startup command.
+ # Default is true.
+ agent_enabled: true
+
+ # Set the name of your application as you'd like it show up in New Relic.
+ # If enable_auto_app_naming is false, the agent reports all data to this application.
+ # Otherwise, the agent reports only background tasks (transactions for non-web applications)
+ # to this application. To report data to more than one application
+ # (useful for rollup reporting), separate the application names with ";".
+ # For example, to report data to "My Application" and "My Application 2" use this:
+ # app_name: My Application;My Application 2
+ # This setting is required. Up to 3 different application names can be specified.
+ # The first application name must be unique.
+ app_name: My Application
+
+ # To enable high security, set this property to true. When in high
+ # security mode, the agent will use SSL and obfuscated SQL. Additionally,
+ # request parameters and message parameters will not be sent to New Relic.
+ high_security: false
+
+ # Set to true to enable support for auto app naming.
+ # The name of each web app is detected automatically
+ # and the agent reports data separately for each one.
+ # This provides a finer-grained performance breakdown for
+ # web apps in New Relic.
+ # Default is false.
+ enable_auto_app_naming: false
+
+ # Set to true to enable component-based transaction naming.
+ # Set to false to use the URI of a web request as the name of the transaction.
+ # Default is true.
+ enable_auto_transaction_naming: true
+
+ # The agent uses its own log file to keep its logging
+ # separate from that of your application. Specify the log level here.
+ # This setting is dynamic, so changes do not require restarting your application.
+ # The levels in increasing order of verboseness are:
+ # off, severe, warning, info, fine, finer, finest
+ # Default is info.
+ log_level: info
+
+ # Log all data sent to and from New Relic in plain text.
+ # This setting is dynamic, so changes do not require restarting your application.
+ # Default is false.
+ audit_mode: false
+
+ # The number of backup log files to save.
+ # Default is 1.
+ log_file_count: 1
+
+ # The maximum number of kbytes to write to any one log file.
+ # The log_file_count must be set greater than 1.
+ # Default is 0 (no limit).
+ log_limit_in_kbytes: 0
+
+ # Override other log rolling configuration and roll the logs daily.
+ # Default is false.
+ log_daily: false
+
+ # The name of the log file.
+ # Default is newrelic_agent.log.
+ log_file_name: newrelic_agent.log
+
+ # The log file directory.
+ # Default is the logs directory in the newrelic.jar parent directory.
+ #log_file_path:
+
+ # The agent communicates with New Relic via https by
+ # default. If you want to communicate with newrelic via http,
+ # then turn off SSL by setting this value to false.
+ # This work is done asynchronously to the threads that process your
+ # application code, so response times will not be directly affected
+ # by this change.
+ # Default is true.
+ ssl: true
+
+ # Proxy settings for connecting to the New Relic server:
+ # If a proxy is used, the host setting is required. Other settings
+ # are optional. Default port is 8080. The username and password
+ # settings will be used to authenticate to Basic Auth challenges
+ # from a proxy server.
+ #proxy_host: hostname
+ #proxy_port: 8080
+ #proxy_user: username
+ #proxy_password: password
+
+ # Limits the number of lines to capture for each stack trace.
+ # Default is 30
+ max_stack_trace_lines: 30
+
+ # Provides the ability to configure the attributes sent to New Relic. These
+ # attributes can be found in transaction traces, traced errors, Insight's
+ # transaction events, and Insight's page views.
+ attributes:
+
+ # When true, attributes will be sent to New Relic. The default is true.
+ enabled: true
+
+ #A comma separated list of attribute keys whose values should
+ # be sent to New Relic.
+ #include:
+
+ # A comma separated list of attribute keys whose values should
+ # not be sent to New Relic.
+ #exclude:
+
+
+ # Transaction tracer captures deep information about slow
+ # transactions and sends this to the New Relic service once a
+ # minute. Included in the transaction is the exact call sequence of
+ # the transactions including any SQL statements issued.
+ transaction_tracer:
+
+ # Transaction tracer is enabled by default. Set this to false to turn it off.
+ # This feature is not available to Lite accounts and is automatically disabled.
+ # Default is true.
+ enabled: true
+
+ # Threshold in seconds for when to collect a transaction
+ # trace. When the response time of a controller action exceeds
+ # this threshold, a transaction trace will be recorded and sent to
+ # New Relic. Valid values are any float value, or (default) "apdex_f",
+ # which will use the threshold for the "Frustrated" Apdex level
+ # (greater than four times the apdex_t value).
+ # Default is apdex_f.
+ transaction_threshold: apdex_f
+
+ # When transaction tracer is on, SQL statements can optionally be
+ # recorded. The recorder has three modes, "off" which sends no
+ # SQL, "raw" which sends the SQL statement in its original form,
+ # and "obfuscated", which strips out numeric and string literals.
+ # Default is obfuscated.
+ record_sql: obfuscated
+
+ # Set this to true to log SQL statements instead of recording them.
+ # SQL is logged using the record_sql mode.
+ # Default is false.
+ log_sql: false
+
+ # Threshold in seconds for when to collect stack trace for a SQL
+ # call. In other words, when SQL statements exceed this threshold,
+ # then capture and send to New Relic the current stack trace. This is
+ # helpful for pinpointing where long SQL calls originate from.
+ # Default is 0.5 seconds.
+ stack_trace_threshold: 0.5
+
+ # Determines whether the agent will capture query plans for slow
+ # SQL queries. Only supported for MySQL and PostgreSQL.
+ # Default is true.
+ explain_enabled: true
+
+ # Threshold for query execution time below which query plans will not
+ # not be captured. Relevant only when `explain_enabled` is true.
+ # Default is 0.5 seconds.
+ explain_threshold: 0.5
+
+ # Use this setting to control the variety of transaction traces.
+ # The higher the setting, the greater the variety.
+ # Set this to 0 to always report the slowest transaction trace.
+ # Default is 20.
+ top_n: 20
+
+ # Error collector captures information about uncaught exceptions and
+ # sends them to New Relic for viewing.
+ error_collector:
+
+ # This property enables the collection of errors. If the property is not
+ # set or the property is set to false, then errors will not be collected.
+ # Default is true.
+ enabled: true
+
+ # Use this property to exclude specific exceptions from being reported as errors
+ # by providing a comma separated list of full class names.
+ # The default is to exclude akka.actor.ActorKilledException. If you want to override
+ # this, you must provide any new value as an empty list is ignored.
+ ignore_errors: akka.actor.ActorKilledException
+
+ # Use this property to exclude specific http status codes from being reported as errors
+ # by providing a comma separated list of status codes.
+ # The default is to exclude 404s. If you want to override
+ # this, you must provide any new value as an empty list is ignored.
+ ignore_status_codes: 404
+
+ # Transaction Events are used for Histograms and Percentiles. Unaggregated data is collected
+ # for each web transaction and sent to the server on harvest.
+ transaction_events:
+
+ # Set to false to disable transaction events.
+ # Default is true.
+ enabled: true
+
+ # Events are collected up to the configured amount. Afterwards, events are sampled to
+ # maintain an even distribution across the harvest cycle.
+ # Default is 2000. Setting to 0 will disable.
+ max_samples_stored: 2000
+
+ # Cross Application Tracing adds request and response headers to
+ # external calls using supported HTTP libraries to provide better
+ # performance data when calling applications monitored by other New Relic Agents.
+ cross_application_tracer:
+
+ # Set to false to disable cross application tracing.
+ # Default is true.
+ enabled: true
+
+ # Thread profiler measures wall clock time, CPU time, and method call counts
+ # in your application's threads as they run.
+ # This feature is not available to Lite accounts and is automatically disabled.
+ thread_profiler:
+
+ # Set to false to disable the thread profiler.
+ # Default is true.
+ enabled: true
+
+ # New Relic Real User Monitoring gives you insight into the performance real users are
+ # experiencing with your website. This is accomplished by measuring the time it takes for
+ # your users' browsers to download and render your web pages by injecting a small amount
+ # of JavaScript code into the header and footer of each page.
+ browser_monitoring:
+
+ # By default the agent automatically inserts API calls in compiled JSPs to
+ # inject the monitoring JavaScript into web pages. Not all rendering engines are supported.
+ # See https://docs.newrelic.com/docs/java/real-user-monitoring-in-java#manual_instrumentation
+ # for instructions to add these manually to your pages.
+ # Set this attribute to false to turn off this behavior.
+ auto_instrument: true
+
+ class_transformer:
+ # This instrumentation reports the name of the user principal returned from
+ # HttpServletRequest.getUserPrincipal() when servlets and filters are invoked.
+ com.newrelic.instrumentation.servlet-user:
+ enabled: false
+
+ com.newrelic.instrumentation.spring-aop-2:
+ enabled: false
+
+ # Classes loaded by classloaders in this list will not be instrumented.
+ # This is a useful optimization for runtimes which use classloaders to
+ # load dynamic classes which the agent would not instrument.
+ classloader_excludes:
+ groovy.lang.GroovyClassLoader$InnerLoader,
+ org.codehaus.groovy.runtime.callsite.CallSiteClassLoader,
+ com.collaxa.cube.engine.deployment.BPELClassLoader,
+ org.springframework.data.convert.ClassGeneratingEntityInstantiator$ObjectInstantiatorClassGenerator,
+ org.mvel2.optimizers.impl.asm.ASMAccessorOptimizer$ContextClassLoader,
+ gw.internal.gosu.compiler.SingleServingGosuClassLoader,
+
+ # User-configurable custom labels for this agent. Labels are name-value pairs.
+ # There is a maximum of 64 labels per agent. Names and values are limited to 255 characters.
+ # Names and values may not contain colons (:) or semicolons (;).
+ labels:
+
+ # An example label
+ #label_name: label_value
+
+
+# Application Environments
+# ------------------------------------------
+# Environment specific settings are in this section.
+# You can use the environment to override the default settings.
+# For example, to change the app_name setting.
+# Use -Dnewrelic.environment= on the Java startup command line
+# to set the environment.
+# The default environment is production.
+
+# NOTE if your application has other named environments, you should
+# provide configuration settings for these environments here.
+
+
+dev:
+ <<: *default_settings
+ app_name: ContentController (Development)
+
+qa:
+ <<: *default_settings
+ app_name: ContentController (qa)
+
+prod:
+ <<: *default_settings
+ app_name: ContentController (Production)
+
+staging:
+ <<: *default_settings
+ app_name: ContentController (Staging)
+
diff --git a/roles/newrelic/templates/newrelic.cfg b/roles/newrelic/templates/newrelic.cfg
new file mode 100644
index 0000000..6e40eaf
--- /dev/null
+++ b/roles/newrelic/templates/newrelic.cfg
@@ -0,0 +1,163 @@
+#
+# This is a configuration template for the New Relic daemon - a communications
+# proxy between New Relic agents (such as the PHP agent) and the New Relic
+# data collectors. This configuration file is required *ONLY* if you need or
+# want to start the New Relic daemon at system boot time using init. In order
+# to do so you must execute the following commands (as root):
+#
+# For CentOS and RedHat systems:
+# /sbin/chkconfig newrelic-daemon on
+#
+# For Ubuntu and Debian systems:
+# /usr/sbin/update-rc.d newrelic-daemon defaults 90 10
+# /usr/sbin/update-rc.d newrelic-daemon enable
+#
+# For other systems please consult your documentation on how to enable and
+# disable services.
+#
+# For all systems:
+# /etc/init.d/newrelic-daemon start
+#
+# The startup scripts will only start the daemon if there is a valid daemon
+# configuration file in place. This file is, by default:
+#
+# /etc/newrelic/newrelic.cfg
+#
+# If that file does not exist, you can copy this template file to that
+# location and edit it to suit your system needs.
+#
+# By default the daemon is not started at system boot time, and does not use
+# the /etc/newrelic/newrelic.cfg file. Rather, the daemon is started by the
+# agent automatically on startup and is configured by the agent (for example
+# when using the PHP agent the daemon parameters are set in the global INI
+# file and all begin with 'newrelic.daemon').
+#
+# There are certain circumstances under which you may want the daemon to be
+# started at boot time rather than by the agent. If you use a chroot jail for
+# running the agent in, if you have multiple web servers or FastCGI process
+# managers, or if you use PHP on the command line a lot for batch processing
+# then you may want to start the daemon once at system boot rather than having
+# the agent start it.
+#
+# Below are the various options you can change that affect the daemon. Each
+# one is explained in detail along with it's default value.
+#
+
+#
+# Setting: pidfile
+# Type : string
+# Purpose: Sets the name of the file that the daemon writes its process ID
+# (PID) to. This is used by the startup and shutdown script to know
+# which process to monitor or kill.
+# Default: None. Init script uses a filename of newrelic-daemon.pid in
+# the first directory from /var/run or /var/pid that is found.
+#pidfile=
+
+#
+# Setting: logfile
+# Type : string
+# Purpose: Sets the name of the file to record log messages in. If this file
+# does not exist it is created. If it cannot be created the daemon
+# will not start up. The amount of information sent to this file is
+# controlled by the loglevel settings, defined below.
+# Default: /var/log/newrelic/newrelic-daemon.log
+#logfile=/var/log/newrelic/newrelic-daemon.log
+
+#
+# Setting: loglevel
+# Type : string
+# Purpose: Sets the level of detail of log messages sent to the log file. This
+# variable can control the log level for different subsystem at
+# different levels, although such custom usage should only be done at
+# the request of New Relic technical support. The simplest setting is
+# to use one of the following keywords, in increasing order of detail:
+# error - only error messages
+# warning - only warning and error messages
+# info - only minimal startup and shutdown info
+# verbose - slightly more verbose than info, not commonly used
+# debug - very verbose, includes messages only relevant to support
+# verbosedebug - extremely verbose, creates massive log files
+# Default: info
+#loglevel=info
+
+#
+# Setting: port
+# Type : String or Integer (1-65534)
+# Purpose: Sets how the agent and daemon communicate. How this is set can impact
+# performance. The default is to use a UNIX-domain socket located at
+# /tmp/.newrelic.sock. If you want to use UNIX domain sockets then
+# this value must begin with a "/". If you set this to an integer
+# value in the range 1-65534, then this will instruct the daemon to
+# listen on a normal TCP socket on the port specified. This may be
+# easier to use if you are using a chroot environment.
+# In order to use a port in the range 1-1023, the daemon must be
+# started by the super-user. This is a fundamental OS limitation
+# and not one imposed by the daemon itself.
+# Default: "/tmp/.newrelic.sock"
+#port="/tmp/.newrelic.sock"
+
+#
+# Setting: ssl
+# Type : boolean
+# Purpose: If you prefer the daemon to use the secure HTTP (https) protocol
+# when communicating with the New Relic collector servers, set this
+# to true.
+# Default: true (as of version 3.6)
+#ssl=true
+
+#
+# Setting: ssl_ca_bundle
+# Type : string
+# Purpose: Sets the location of a file containing CA certificates in PEM
+# format. When set, the certificates in this file will be used
+# to authenticate the New Relic collector servers. If ssl_ca_path
+# is also set (see below), the certificates in this file will be
+# searched first, followed by the certificates contained in the
+# ssl_ca_path directory. This setting has no effect when ssl
+# is set to false.
+# Default: none
+#ssl_ca_bundle=
+
+#
+# Setting: ssl_ca_path
+# Type : string
+# Purpose: Sets the location of a directory containing trusted CA certificates
+# in PEM format. When set, the certificates in this directory will be
+# used to authenticate the New Relic collector servers. If
+# ssl_ca_bundle is also set (see above), it will be searched first
+# followed by the certificates contained in ssl_ca_path. This
+# setting has no effect when ssl is set to false.
+# Default: none
+#ssl_ca_path=
+
+#
+# Setting: proxy
+# Type : string
+# Purpose: Some networks are configured to require the use of an egress proxy
+# server in order to communicate with the outside world. Since the
+# daemon needs to communicate with the New Relic data collection
+# servers you may need to instruct it to use a proxy server. Your
+# system or network administrator should be able to provide you with
+# the details.
+# This string is in the form [user[:password]]@hostname[:port] with
+# the user, password and port fields being optional. Some examples:
+# myusername:secret@10.1.1.1:12345
+# someuser@proxy.mydomain.com:4321
+# proxy.mydomain.com
+# Default: none
+#proxy=
+
+#
+# Setting: auditlog
+# Type : string
+# Purpose: Sets the name of a file to record all uncompressed, un-encoded
+# content that is sent from your machine to the New Relic servers.
+# This includes the full URL for each command along with the payload
+# delivered with the command. This allows you to satisfy yourself
+# that the agent is not sending any sensitive data to our servers.
+# This file must be a different file to the logfile setting above.
+# If you set it to the same name audit logging will be silently
+# ignored.
+# Default: None
+#auditlog=/var/log/newrelic/audit.log
+
diff --git a/roles/newrelic/templates/newrelic.engine.yml b/roles/newrelic/templates/newrelic.engine.yml
new file mode 100644
index 0000000..90442e7
--- /dev/null
+++ b/roles/newrelic/templates/newrelic.engine.yml
@@ -0,0 +1,298 @@
+# This file configures the New Relic Agent. New Relic monitors
+# Java applications with deep visibility and low overhead. For more details and additional
+# configuration options visit https://docs.newrelic.com/docs/java/java-agent-configuration.
+#
+# This configuration file is custom generated for Rustici Software, LLC
+#
+# This section is for settings common to all environments.
+# Do not add anything above this next line.
+common: &default_settings
+
+ # ============================== LICENSE KEY ===============================
+ # You must specify the license key associated with your New Relic
+ # account. For example, if your license key is 12345 use this:
+ # license_key: '12345'
+ # The key binds your Agent's data to your account in the New Relic service.
+ license_key: {{ newrelic_license_key }}
+
+ # Agent Enabled
+ # Use this setting to disable the agent instead of removing it from the startup command.
+ # Default is true.
+ agent_enabled: true
+
+ # Set the name of your application as you'd like it show up in New Relic.
+ # If enable_auto_app_naming is false, the agent reports all data to this application.
+ # Otherwise, the agent reports only background tasks (transactions for non-web applications)
+ # to this application. To report data to more than one application
+ # (useful for rollup reporting), separate the application names with ";".
+ # For example, to report data to "My Application" and "My Application 2" use this:
+ # app_name: Scorm Cloud;My Application 2
+ # This setting is required.
+ #app_name: Scorm Cloud
+ # JHM - We handle this at the bottom of this config file
+ # Via the "environments" settings
+ ##########
+
+
+ # To enable high security, set this property to true. When in high
+ # security mode, the agent will use SSL and obfuscated SQL. Additionally,
+ # request parameters and message parameters will not be sent to New Relic.
+ high_security: false
+
+ # Set to true to enable support for auto app naming.
+ # The name of each web app is detected automatically
+ # and the agent reports data separately for each one.
+ # This provides a finer-grained performance breakdown for
+ # web apps in New Relic.
+ # Default is false.
+ enable_auto_app_naming: false
+
+ # Set to true to enable component-based transaction naming.
+ # Set to false to use the URI of a web request as the name of the transaction.
+ # Default is true.
+ enable_auto_transaction_naming: true
+
+ # The agent uses its own log file to keep its logging
+ # separate from that of your application. Specify the log level here.
+ # This setting is dynamic, so changes do not require restarting your application.
+ # The levels in increasing order of verboseness are:
+ # off, severe, warning, info, fine, finer, finest
+ # Default is info.
+ log_level: info
+
+ # Log all data sent to and from New Relic in plain text.
+ # This setting is dynamic, so changes do not require restarting your application.
+ # Default is false.
+ audit_mode: false
+
+ # The number of backup log files to save.
+ # Default is 1.
+ log_file_count: 1
+
+ # The maximum number of kbytes to write to any one log file.
+ # The log_file_count must be set greater than 1.
+ # Default is 0 (no limit).
+ log_limit_in_kbytes: 0
+
+ # Override other log rolling configuration and roll the logs daily.
+ # Default is false.
+ log_daily: false
+
+ # The name of the log file.
+ # Default is newrelic_agent.log.
+ log_file_name: newrelic-java-agent.log
+
+ # The log file directory.
+ # Default is the logs directory in the newrelic.jar parent directory.
+ log_file_path: /var/log/newrelic
+
+ # The agent communicates with New Relic via https by
+ # default. If you want to communicate with newrelic via http,
+ # then turn off SSL by setting this value to false.
+ # This work is done asynchronously to the threads that process your
+ # application code, so response times will not be directly affected
+ # by this change.
+ # Default is true.
+ ssl: true
+
+ # Proxy settings for connecting to the New Relic server:
+ # If a proxy is used, the host setting is required. Other settings
+ # are optional. Default port is 8080. The username and password
+ # settings will be used to authenticate to Basic Auth challenges
+ # from a proxy server.
+ #proxy_host: hostname
+ #proxy_port: 8080
+ #proxy_user: username
+ #proxy_password: password
+
+ # Limits the number of lines to capture for each stack trace.
+ # Default is 30
+ max_stack_trace_lines: 30
+
+ # Provides the ability to configure the attributes sent to New Relic. These
+ # attributes can be found in transaction traces, traced errors, Insight's
+ # transaction events, and Insight's page views.
+ attributes:
+
+ # When true, attributes will be sent to New Relic. The default is true.
+ enabled: true
+
+ #A comma separated list of attribute keys whose values should
+ # be sent to New Relic.
+ #include:
+
+ # A comma separated list of attribute keys whose values should
+ # not be sent to New Relic.
+ #exclude:
+
+
+ # Transaction tracer captures deep information about slow
+ # transactions and sends this to the New Relic service once a
+ # minute. Included in the transaction is the exact call sequence of
+ # the transactions including any SQL statements issued.
+ transaction_tracer:
+
+ # Transaction tracer is enabled by default. Set this to false to turn it off.
+ # This feature is not available to Lite accounts and is automatically disabled.
+ # Default is true.
+ enabled: true
+
+ # Threshold in seconds for when to collect a transaction
+ # trace. When the response time of a controller action exceeds
+ # this threshold, a transaction trace will be recorded and sent to
+ # New Relic. Valid values are any float value, or (default) "apdex_f",
+ # which will use the threshold for the "Frustrated" Apdex level
+ # (greater than four times the apdex_t value).
+ # Default is apdex_f.
+ transaction_threshold: apdex_f
+
+ # When transaction tracer is on, SQL statements can optionally be
+ # recorded. The recorder has three modes, "off" which sends no
+ # SQL, "raw" which sends the SQL statement in its original form,
+ # and "obfuscated", which strips out numeric and string literals.
+ # Default is obfuscated.
+ record_sql: obfuscated
+
+ # Set this to true to log SQL statements instead of recording them.
+ # SQL is logged using the record_sql mode.
+ # Default is false.
+ log_sql: false
+
+ # Threshold in seconds for when to collect stack trace for a SQL
+ # call. In other words, when SQL statements exceed this threshold,
+ # then capture and send to New Relic the current stack trace. This is
+ # helpful for pinpointing where long SQL calls originate from.
+ # Default is 0.5 seconds.
+ stack_trace_threshold: 0.5
+
+ # Determines whether the agent will capture query plans for slow
+ # SQL queries. Only supported for MySQL and PostgreSQL.
+ # Default is true.
+ explain_enabled: true
+
+ # Threshold for query execution time below which query plans will not
+ # not be captured. Relevant only when `explain_enabled` is true.
+ # Default is 0.5 seconds.
+ explain_threshold: 0.5
+
+ # Use this setting to control the variety of transaction traces.
+ # The higher the setting, the greater the variety.
+ # Set this to 0 to always report the slowest transaction trace.
+ # Default is 20.
+ top_n: 20
+
+ # Error collector captures information about uncaught exceptions and
+ # sends them to New Relic for viewing.
+ error_collector:
+
+ # This property enables the collection of errors. If the property is not
+ # set or the property is set to false, then errors will not be collected.
+ # Default is true.
+ enabled: true
+
+ # Use this property to exclude specific exceptions from being reported as errors
+ # by providing a comma separated list of full class names.
+ # The default is to exclude akka.actor.ActorKilledException. If you want to override
+ # this, you must provide any new value as an empty list is ignored.
+ ignore_errors: akka.actor.ActorKilledException
+
+ # Use this property to exclude specific http status codes from being reported as errors
+ # by providing a comma separated list of status codes.
+ # The default is to exclude 404s. If you want to override
+ # this, you must provide any new value as an empty list is ignored.
+ ignore_status_codes: 404
+
+ # Transaction Events are used for Histograms and Percentiles. Unaggregated data is collected
+ # for each web transaction and sent to the server on harvest.
+ transaction_events:
+
+ # Set to false to disable transaction events.
+ # Default is true.
+ enabled: true
+
+ # Events are collected up to the configured amount. Afterwards, events are sampled to
+ # maintain an even distribution across the harvest cycle. Largest value accepted is 10000.
+ # Default is 2000. Setting to 0 will disable.
+ max_samples_stored: 2000
+
+ # Cross Application Tracing adds request and response headers to
+ # external calls using supported HTTP libraries to provide better
+ # performance data when calling applications monitored by other New Relic Agents.
+ cross_application_tracer:
+
+ # Set to false to disable cross application tracing.
+ # Default is true.
+ enabled: true
+
+ # Thread profiler measures wall clock time, CPU time, and method call counts
+ # in your application's threads as they run.
+ # This feature is not available to Lite accounts and is automatically disabled.
+ thread_profiler:
+
+ # Set to false to disable the thread profiler.
+ # Default is true.
+ enabled: true
+
+ # New Relic Real User Monitoring gives you insight into the performance real users are
+ # experiencing with your website. This is accomplished by measuring the time it takes for
+ # your users' browsers to download and render your web pages by injecting a small amount
+ # of JavaScript code into the header and footer of each page.
+ browser_monitoring:
+
+ # By default the agent automatically inserts API calls in compiled JSPs to
+ # inject the monitoring JavaScript into web pages. Not all rendering engines are supported.
+ # See https://docs.newrelic.com/docs/java/real-user-monitoring-in-java#manual_instrumentation
+ # for instructions to add these manually to your pages.
+ # Set this attribute to false to turn off this behavior.
+ auto_instrument: true
+
+ class_transformer:
+ # This instrumentation reports the name of the user principal returned from
+ # HttpServletRequest.getUserPrincipal() when servlets and filters are invoked.
+ com.newrelic.instrumentation.servlet-user:
+ enabled: false
+
+ com.newrelic.instrumentation.spring-aop-2:
+ enabled: false
+
+ # User-configurable custom labels for this agent. Labels are name-value pairs.
+ # There is a maximum of 64 labels per agent. Names and values are limited to 255 characters.
+ # Names and values may not contain colons (:) or semicolons (;).
+ labels: Environment:{{ env }}
+
+ # An example label
+ #label_name: label_value
+
+
+# Application Environments
+# ------------------------------------------
+# Environment specific settings are in this section.
+# You can use the environment to override the default settings.
+# For example, to change the app_name setting.
+# Use -Dnewrelic.environment= on the Java startup command line
+# to set the environment.
+# The default environment is production.
+
+# NOTE if your application has other named environments, you should
+# provide configuration settings for these environments here.
+
+dev:
+ <<: *default_settings
+ app_name: SCORMEngine (Development)
+
+qa:
+ <<: *default_settings
+ app_name: SCORMEngine (qa)
+
+prod:
+ <<: *default_settings
+ app_name: SCORMEngine (Production)
+
+production:
+ <<: *default_settings
+ app_name: SCORMEngine (Production)
+
+staging:
+ <<: *default_settings
+ app_name: SCORMEngine (Staging)
+
diff --git a/roles/newrelic/templates/nrsysmond-hostname.sh b/roles/newrelic/templates/nrsysmond-hostname.sh
new file mode 100644
index 0000000..3d01502
--- /dev/null
+++ b/roles/newrelic/templates/nrsysmond-hostname.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+FILE=/etc/newrelic/nrsysmond.cfg
+
+grep -q '^hostname=' "$FILE" || echo "hostname=$(hostname --fqdn)" >> "$FILE"
diff --git a/roles/saml/tasks/main.yml b/roles/saml/tasks/main.yml
new file mode 100644
index 0000000..af2bae7
--- /dev/null
+++ b/roles/saml/tasks/main.yml
@@ -0,0 +1,14 @@
+---
+- name: Deploy SAML files
+ copy:
+ src: "{{ item.name }}"
+ dest: "{{ item.dest }}/{{ item.name }}"
+ owner: tomcat
+ group: tomcat
+ mode: 0640
+ with_items:
+ - {name: "samlKeystore.jks", dest: "{{ cc_deploy_path }}"}
+ - {name: "idp-metadata.xml", dest: "{{ cc_deploy_path }}"}
+ # These items are to support version of CC from before we moved the SAML files into cc_deploy_path
+ - {name: "samlKeystore.jks", dest: "{{ data_root }}"}
+ - {name: "idp-metadata.xml", dest: "{{ data_root }}"}
diff --git a/roles/ssl/defaults/main.yml b/roles/ssl/defaults/main.yml
new file mode 100644
index 0000000..cddc26b
--- /dev/null
+++ b/roles/ssl/defaults/main.yml
@@ -0,0 +1,7 @@
+---
+
+use_self_signed: false
+
+db_use_ssl: false
+db_ssl_protocols: "TLSv1,TLSv1.1,TLSv1.2"
+db_ssl_cert: "rds-ca-2019-root.crt"
\ No newline at end of file
diff --git a/roles/ssl/files/rds-ca-2019-root.crt b/roles/ssl/files/rds-ca-2019-root.crt
new file mode 100644
index 0000000..6eed46f
Binary files /dev/null and b/roles/ssl/files/rds-ca-2019-root.crt differ
diff --git a/roles/ssl/meta/main.yml b/roles/ssl/meta/main.yml
new file mode 100644
index 0000000..cd23b5d
--- /dev/null
+++ b/roles/ssl/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - { role: java, when: use_self_signed }
diff --git a/roles/ssl/tasks/main.yml b/roles/ssl/tasks/main.yml
new file mode 100644
index 0000000..90131a1
--- /dev/null
+++ b/roles/ssl/tasks/main.yml
@@ -0,0 +1,61 @@
+---
+
+ - name: Install SSL Public certificate
+ copy: src={{ ssl_certificate }} dest="/etc/ssl/certs/{{ ssl_certificate }}" owner=root group=root mode=0644
+ when: not use_self_signed and use_ssl
+
+ - name: Link directories on RedHat
+ file:
+ state: link
+ src: /etc/pki/tls/private
+ dest: /etc/ssl/private
+ when: ansible_os_family == 'RedHat'
+ ignore_errors: true
+
+ - name: Install SSL Private key (Ubuntu)
+ copy: src={{ ssl_key }} dest="/etc/ssl/private/{{ ssl_key }}" owner=root group=ssl-cert mode=0640
+ when: not use_self_signed and use_ssl and ansible_os_family == 'Debian'
+
+ - name: Install SSL Private key (RedHat)
+ copy: src={{ ssl_key }} dest="/etc/ssl/private/{{ ssl_key }}" owner=root group=root mode=0600
+ when: not use_self_signed and use_ssl and ansible_os_family == 'RedHat'
+
+ - name: Install Certificate Chain
+ copy: src={{ ssl_chain }} dest="/etc/ssl/certs/{{ ssl_chain }}" owner=root group=root mode=0644
+ when: not use_self_signed and use_ssl and ssl_chain != None
+
+ - name: Copy over SSL Self-signed cert generation script
+ template: src=genSelfSigned.sh.j2 dest=/usr/local/sbin/genSelfSigned.sh mode=0700 owner=root group=root
+ when: use_self_signed and use_ssl and ansible_os_family == 'Debian'
+
+ - name: create self-signed SSL cert and import into java keystore
+ shell: /usr/local/sbin/genSelfSigned.sh
+ notify: restart httpd
+ register: createssl
+ when: use_self_signed and use_ssl and ansible_os_family == 'Debian'
+
+ - name: Output from Create SSL script
+ debug: msg= {{ createssl.stdout_lines }}
+ ignore_errors: true
+ when: use_self_signed and use_ssl and ansible_os_family == 'Debian'
+
+ - name: Validate DB SSL arguments
+ fail:
+ msg: "db_use_ssl was set to true, but db_ssl_cert was not provided"
+ when: db_use_ssl and db_ssl_cert is not defined
+
+ - name: Copy DB SSL Certificate
+ copy:
+ src: "{{ db_ssl_cert }}"
+ dest: "/tmp/{{ db_ssl_cert }}"
+ mode: 0644
+ when: db_use_ssl
+
+ - name: Import local SSL certificate into the java keystore
+ java_cert:
+ cert_alias: cc_database
+ cert_path: "/tmp/{{ db_ssl_cert }}"
+ keystore_path: "{{ java_home }}{{ '' if is_java_11 else '/jre' }}/lib/security/cacerts"
+ keystore_pass: changeit
+ state: present
+ when: db_use_ssl
diff --git a/roles/ssl/tasks/upload-ssl-to-IAM.yml b/roles/ssl/tasks/upload-ssl-to-IAM.yml
new file mode 100644
index 0000000..c96543a
--- /dev/null
+++ b/roles/ssl/tasks/upload-ssl-to-IAM.yml
@@ -0,0 +1,8 @@
+---
+
+ - name: Upload certificate to AWS IAM object
+ shell: aws iam upload-server-certificate --server-certificate-name star_cloud_scorm_com --certificate-body file://star_cloud_scorm_com.crt --private-key file://star_cloud_scorm_com.key --certificate-chain file://gd_bundle-g2-g1.crt
+ register: sslupload
+
+ - name: Show results of upload-server-certificate
+ debug: msg="{{ sslupload['ServerCertificateMetadata'] }}"
diff --git a/roles/ssl/templates/genSelfSigned.sh.j2 b/roles/ssl/templates/genSelfSigned.sh.j2
new file mode 100644
index 0000000..8442ea0
--- /dev/null
+++ b/roles/ssl/templates/genSelfSigned.sh.j2
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+{% if use_self_signed %}
+
+CN=`openssl x509 -noout -subject -in /etc/ssl/certs/{{ ssl_certificate }} | sed -n '/^subject/s/^.*CN=//p'`
+CPCN=`openssl x509 -noout -subject -in /etc/ssl/certs/{{ ssl_certificate }} | sed -n '/^subject/s/^.*CN=//p'`
+SERVERNAME="{{ ServerName }}"
+{% if ContentPortalServerName is defined %}
+CP_SERVERNAME="{{ ContentPortalServerName }}"
+{% endif %}
+
+if [[ $CN != $SERVERNAME ]]
+ then
+ openssl req -new -nodes -x509 -subj "/C=US/ST=Tennessee/L=Franklin/O=IT/CN={{ ServerName }}" -days 3650 -keyout "/etc/ssl/private/{{ ssl_key }}" -out "/etc/ssl/certs/{{ ssl_certificate }}" -extensions v3_ca
+fi
+
+{% if ContentPortalServerName is defined %}
+if [[ $CPCN != CP_SERVERNAME ]]
+ then
+ openssl req -new -nodes -x509 -subj "/C=US/ST=Tennessee/L=Franklin/O=IT/CN={{ ContentPortalServerName }}" -days 3650 -keyout "/etc/ssl/private/{{ cp_ssl_key }}" -out "/etc/ssl/certs/{{ cp_ssl_certificate }}" -extensions v3_ca
+fi
+{% endif %}
+
+{% else %}
+
+echo "The var use_self_signed is false, so I am not generating a certificate."
+
+{% endif %}
+
+CA_CERTS=`find {{ java_home }} -name cacerts`
+
+# Remove the existing cert if it is in the keystore
+{{ java_home }}/bin/keytool -delete -noprompt -alias {{ ServerName }} -keystore $CA_CERTS -storepass changeit
+
+# Insert the new certificate
+{{ java_home }}/bin/keytool -import -trustcacerts -alias {{ ServerName }} -file /etc/ssl/certs/{{ ssl_certificate }} -keystore $CA_CERTS -noprompt -storepass changeit
+
+{% if ContentPortalServerName is defined %}
+
+# Remove the existing cert if it is in the keystore
+{{ java_home }}/bin/keytool -delete -noprompt -alias {{ ContentPortalServerName }} -keystore $CA_CERTS -storepass changeit
+
+# Insert the new certificate
+{{ java_home }}/bin/keytool -import -trustcacerts -alias {{ ContentPortalServerName }} -file /etc/ssl/certs/{{ cp_ssl_certificate }} -keystore $CA_CERTS -noprompt -storepass changeit
+{% endif %}
\ No newline at end of file
diff --git a/roles/tomcat/defaults/main.yml b/roles/tomcat/defaults/main.yml
new file mode 100644
index 0000000..0959fc2
--- /dev/null
+++ b/roles/tomcat/defaults/main.yml
@@ -0,0 +1,18 @@
+---
+
+# Where tomcat logs to:
+tomcat_log_path: /usr/share/tomcat/logs
+
+# Java Heap Sizes
+# On production environments, tt is VERY important to set these variable to match your target system!
+# To choose your heap sizes, take the amount of RAM in your system, subtract the OS overhead, and subtract another 50% of the OS overhead.
+# Split the remaining RAM between the engine_heap_size and cc_heap size.
+# So, if you have 8GB RAM in your box, and your OS needs 1GB to run+50%, that leaves you 6.5GB to allocate.
+
+# A good rule of thumb for allocation is that you should give 2/3 of the available memory to Engine,
+# and 1/3 to Content Controller.
+# You'll want to allocate a MINIMUM of 500m to cc_heap size and 1g to the engine_heap_size.
+engine_heap_size: 1g
+cc_heap_size: 500m
+
+tomcat_version: "9.0.75"
diff --git a/roles/tomcat/files/catalina.policy b/roles/tomcat/files/catalina.policy
new file mode 100644
index 0000000..49d311a
--- /dev/null
+++ b/roles/tomcat/files/catalina.policy
@@ -0,0 +1,279 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ============================================================================
+// catalina.policy - Security Policy Permissions for Tomcat
+//
+// This file contains a default set of security policies to be enforced (by the
+// JVM) when Catalina is executed with the "-security" option. In addition
+// to the permissions granted here, the following additional permissions are
+// granted to each web application:
+//
+// * Read access to the web application's document root directory
+// * Read, write and delete access to the web application's working directory
+// ============================================================================
+
+
+// ========== SYSTEM CODE PERMISSIONS =========================================
+
+
+// These permissions apply to javac
+grant codeBase "file:${java.home}/lib/-" {
+ permission java.security.AllPermission;
+};
+
+// These permissions apply to all shared system extensions
+grant codeBase "file:${java.home}/jre/lib/ext/-" {
+ permission java.security.AllPermission;
+};
+
+// These permissions apply to javac when ${java.home} points at $JAVA_HOME/jre
+grant codeBase "file:${java.home}/../lib/-" {
+ permission java.security.AllPermission;
+};
+
+// These permissions apply to all shared system extensions when
+// ${java.home} points at $JAVA_HOME/jre
+grant codeBase "file:${java.home}/lib/ext/-" {
+ permission java.security.AllPermission;
+};
+
+// This permission is required when using javac to compile JSPs on Java 9
+// onwards
+//grant codeBase "jrt:/jdk.compiler" {
+// permission java.security.AllPermission;
+//};
+
+
+// ========== CATALINA CODE PERMISSIONS =======================================
+
+// These permissions apply to the daemon code
+grant codeBase "file:${catalina.home}/bin/commons-daemon.jar" {
+ permission java.security.AllPermission;
+};
+
+// These permissions apply to the logging API
+// Note: If tomcat-juli.jar is in ${catalina.base} and not in ${catalina.home},
+// update this section accordingly.
+// grant codeBase "file:${catalina.base}/bin/tomcat-juli.jar" {..}
+grant codeBase "file:${catalina.home}/bin/tomcat-juli.jar" {
+ permission java.io.FilePermission
+ "${java.home}${file.separator}lib${file.separator}logging.properties", "read";
+
+ permission java.io.FilePermission
+ "${catalina.base}${file.separator}conf${file.separator}logging.properties", "read";
+ permission java.io.FilePermission
+ "${catalina.base}${file.separator}logs", "read, write";
+ permission java.io.FilePermission
+ "${catalina.base}${file.separator}logs${file.separator}*", "read, write, delete";
+
+ permission java.lang.RuntimePermission "shutdownHooks";
+ permission java.lang.RuntimePermission "getClassLoader";
+ permission java.lang.RuntimePermission "setContextClassLoader";
+
+ permission java.lang.management.ManagementPermission "monitor";
+
+ permission java.util.logging.LoggingPermission "control";
+
+ permission java.util.PropertyPermission "java.util.logging.config.class", "read";
+ permission java.util.PropertyPermission "java.util.logging.config.file", "read";
+ permission java.util.PropertyPermission "org.apache.juli.AsyncLoggerPollInterval", "read";
+ permission java.util.PropertyPermission "org.apache.juli.AsyncMaxRecordCount", "read";
+ permission java.util.PropertyPermission "org.apache.juli.AsyncOverflowDropType", "read";
+ permission java.util.PropertyPermission "org.apache.juli.ClassLoaderLogManager.debug", "read";
+ permission java.util.PropertyPermission "catalina.base", "read";
+
+ // Note: To enable per context logging configuration, permit read access to
+ // the appropriate file. Be sure that the logging configuration is
+ // secure before enabling such access.
+ // E.g. for the examples web application (uncomment and unwrap
+ // the following to be on a single line):
+ // permission java.io.FilePermission "${catalina.base}${file.separator}
+ // webapps${file.separator}examples${file.separator}WEB-INF
+ // ${file.separator}classes${file.separator}logging.properties", "read";
+};
+
+// These permissions apply to the server startup code
+grant codeBase "file:${catalina.home}/bin/bootstrap.jar" {
+ permission java.security.AllPermission;
+};
+
+// These permissions apply to the servlet API classes
+// and those that are shared across all class loaders
+// located in the "lib" directory
+grant codeBase "file:${catalina.home}/lib/-" {
+ permission java.security.AllPermission;
+};
+
+
+// If using a per instance lib directory, i.e. ${catalina.base}/lib,
+// then the following permission will need to be uncommented
+// grant codeBase "file:${catalina.base}/lib/-" {
+// permission java.security.AllPermission;
+// };
+
+
+// ========== WEB APPLICATION PERMISSIONS =====================================
+
+
+// These permissions are granted by default to all web applications
+// In addition, a web application will be given a read FilePermission
+// for all files and directories in its document root.
+grant {
+ // Required for JNDI lookup of named JDBC DataSource's and
+ // javamail named MimePart DataSource used to send mail
+ permission java.util.PropertyPermission "java.home", "read";
+ permission java.util.PropertyPermission "java.naming.*", "read";
+ permission java.util.PropertyPermission "javax.sql.*", "read";
+
+ // OS Specific properties to allow read access
+ permission java.util.PropertyPermission "os.name", "read";
+ permission java.util.PropertyPermission "os.version", "read";
+ permission java.util.PropertyPermission "os.arch", "read";
+ permission java.util.PropertyPermission "file.separator", "read";
+ permission java.util.PropertyPermission "path.separator", "read";
+ permission java.util.PropertyPermission "line.separator", "read";
+
+ // JVM properties to allow read access
+ permission java.util.PropertyPermission "java.version", "read";
+ permission java.util.PropertyPermission "java.vendor", "read";
+ permission java.util.PropertyPermission "java.vendor.url", "read";
+ permission java.util.PropertyPermission "java.class.version", "read";
+ permission java.util.PropertyPermission "java.specification.version", "read";
+ permission java.util.PropertyPermission "java.specification.vendor", "read";
+ permission java.util.PropertyPermission "java.specification.name", "read";
+
+ permission java.util.PropertyPermission "java.vm.specification.version", "read";
+ permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
+ permission java.util.PropertyPermission "java.vm.specification.name", "read";
+ permission java.util.PropertyPermission "java.vm.version", "read";
+ permission java.util.PropertyPermission "java.vm.vendor", "read";
+ permission java.util.PropertyPermission "java.vm.name", "read";
+
+ // Required for OpenJMX
+ permission java.lang.RuntimePermission "getAttribute";
+
+ // Allow read of JAXP compliant XML parser debug
+ permission java.util.PropertyPermission "jaxp.debug", "read";
+
+ // All JSPs need to be able to read this package
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.tomcat";
+
+ // Precompiled JSPs need access to these packages.
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.jasper.el";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.jasper.runtime";
+ permission java.lang.RuntimePermission
+ "accessClassInPackage.org.apache.jasper.runtime.*";
+
+ // Applications using WebSocket need to be able to access these packages
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.tomcat.websocket";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.tomcat.websocket.server";
+};
+
+
+// The Manager application needs access to the following packages to support the
+// session display functionality. It also requires the custom Tomcat
+// DeployXmlPermission to enable the use of META-INF/context.xml
+// These settings support the following configurations:
+// - default CATALINA_HOME == CATALINA_BASE
+// - CATALINA_HOME != CATALINA_BASE, per instance Manager in CATALINA_BASE
+// - CATALINA_HOME != CATALINA_BASE, shared Manager in CATALINA_HOME
+grant codeBase "file:${catalina.base}/webapps/manager/-" {
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.ha.session";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.manager";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.manager.util";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.util";
+ permission org.apache.catalina.security.DeployXmlPermission "manager";
+};
+grant codeBase "file:${catalina.home}/webapps/manager/-" {
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.ha.session";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.manager";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.manager.util";
+ permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.util";
+ permission org.apache.catalina.security.DeployXmlPermission "manager";
+};
+
+// The Host Manager application needs the custom Tomcat DeployXmlPermission to
+// enable the use of META-INF/context.xml
+// These settings support the following configurations:
+// - default CATALINA_HOME == CATALINA_BASE
+// - CATALINA_HOME != CATALINA_BASE, per instance Host Manager in CATALINA_BASE
+// - CATALINA_HOME != CATALINA_BASE, shared Host Manager in CATALINA_HOME
+grant codeBase "file:${catalina.base}/webapps/host-manager/-" {
+ permission org.apache.catalina.security.DeployXmlPermission "host-manager";
+};
+
+grant codeBase "file:${catalina.home}/webapps/host-manager/-" {
+ permission org.apache.catalina.security.DeployXmlPermission "host-manager";
+};
+
+// Changes required for Rustici Engine
+grant codeBase "file:${catalina.base}/webapps/ScormEngineInterface/-" {
+ permission java.io.FilePermission "<>", "read,write,delete";
+ permission java.lang.reflect.ReflectPermission "*";
+ permission java.lang.RuntimePermission "*";
+ permission java.net.NetPermission "*";
+ permission java.net.SocketPermission "*", "connect,resolve";
+ permission java.net.URLPermission "http:*";
+ permission java.net.URLPermission "https:*";
+ permission java.security.SecurityPermission "putProviderProperty.BC";
+ permission java.security.SecurityPermission "insertProvider.BC";
+ permission java.sql.SQLPermission "*";
+ permission java.util.logging.LoggingPermission "control";
+ permission java.util.PropertyPermission "*", "read,write";
+};
+
+// You can assign additional permissions to particular web applications by
+// adding additional "grant" entries here, based on the code base for that
+// application, /WEB-INF/classes/, or /WEB-INF/lib/ jar files.
+//
+// Different permissions can be granted to JSP pages, classes loaded from
+// the /WEB-INF/classes/ directory, all jar files in the /WEB-INF/lib/
+// directory, or even to individual jar files in the /WEB-INF/lib/ directory.
+//
+// For instance, assume that the standard "examples" application
+// included a JDBC driver that needed to establish a network connection to the
+// corresponding database and used the scrape taglib to get the weather from
+// the NOAA web server. You might create a "grant" entries like this:
+//
+// The permissions granted to the context root directory apply to JSP pages.
+// grant codeBase "file:${catalina.base}/webapps/examples/-" {
+// permission java.net.SocketPermission "dbhost.mycompany.com:5432", "connect";
+// permission java.net.SocketPermission "*.noaa.gov:80", "connect";
+// };
+//
+// The permissions granted to the context WEB-INF/classes directory
+// grant codeBase "file:${catalina.base}/webapps/examples/WEB-INF/classes/-" {
+// };
+//
+// The permission granted to your JDBC driver
+// grant codeBase "jar:file:${catalina.base}/webapps/examples/WEB-INF/lib/driver.jar!/-" {
+// permission java.net.SocketPermission "dbhost.mycompany.com:5432", "connect";
+// };
+// The permission granted to the scrape taglib
+// grant codeBase "jar:file:${catalina.base}/webapps/examples/WEB-INF/lib/scrape.jar!/-" {
+// permission java.net.SocketPermission "*.noaa.gov:80", "connect";
+// };
+
+// To grant permissions for web applications using packed WAR files, use the
+// Tomcat specific WAR url scheme.
+//
+
+//
+// The permissions granted to a specific JAR
+// grant codeBase "war:file:${catalina.base}/webapps/examples.war*/WEB-INF/lib/foo.jar" {
+// };
\ No newline at end of file
diff --git a/roles/tomcat/files/tomcat-jdbc-9.0.71.jar b/roles/tomcat/files/tomcat-jdbc-9.0.71.jar
new file mode 100644
index 0000000..14edfb4
Binary files /dev/null and b/roles/tomcat/files/tomcat-jdbc-9.0.71.jar differ
diff --git a/roles/tomcat/files/tomcat-jdbc-9.0.72.jar b/roles/tomcat/files/tomcat-jdbc-9.0.72.jar
new file mode 100644
index 0000000..5a8fa6a
Binary files /dev/null and b/roles/tomcat/files/tomcat-jdbc-9.0.72.jar differ
diff --git a/roles/tomcat/files/tomcat-jdbc-9.0.75.jar b/roles/tomcat/files/tomcat-jdbc-9.0.75.jar
new file mode 100644
index 0000000..ed4ddbc
Binary files /dev/null and b/roles/tomcat/files/tomcat-jdbc-9.0.75.jar differ
diff --git a/roles/tomcat/handlers/main.yml b/roles/tomcat/handlers/main.yml
new file mode 100644
index 0000000..dbc8dff
--- /dev/null
+++ b/roles/tomcat/handlers/main.yml
@@ -0,0 +1,12 @@
+---
+
+ - name: start tomcat
+ action: service name=tomcat state=started
+ notify: restart httpd
+
+ - name: restart tomcat
+ action: service name=tomcat state=restarted
+ notify: restart httpd
+
+ - name: stop tomcat
+ action: service name=tomcat state=stopped
\ No newline at end of file
diff --git a/roles/tomcat/meta/main.yml b/roles/tomcat/meta/main.yml
new file mode 100644
index 0000000..ffaed26
--- /dev/null
+++ b/roles/tomcat/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+ - { role: java }
diff --git a/roles/tomcat/tasks/main.yml b/roles/tomcat/tasks/main.yml
new file mode 100644
index 0000000..2492603
--- /dev/null
+++ b/roles/tomcat/tasks/main.yml
@@ -0,0 +1,130 @@
+---
+ - name: add group "tomcat"
+ group: name=tomcat
+
+ - name: add user "tomcat"
+ user: name=tomcat group=tomcat home={{ tomcat_base }} createhome=no
+ become: true
+
+ - name: Download Tomcat
+ get_url:
+ url: "https://archive.apache.org/dist/tomcat/tomcat-9/v{{ tomcat_version }}/bin/apache-tomcat-{{ tomcat_version }}.tar.gz"
+ dest: "/opt/apache-tomcat-{{ tomcat_version }}.tar.gz"
+
+ - name: Extract archive
+ command: "chdir=/usr/share /bin/tar xvf /opt/apache-tomcat-{{ tomcat_version }}.tar.gz -C /opt/ creates=/opt/apache-tomcat-{{ tomcat_version }}"
+
+ - name: set name of tomcat directory
+ set_fact:
+ tomcat_dir: "/opt/apache-tomcat-{{ tomcat_version }}"
+
+ - name: Symlink install directory
+ file:
+ src: "/opt/apache-tomcat-{{ tomcat_version }}"
+ path: "{{ tomcat_base }}"
+ state: link
+
+ - name: Change ownership of Tomcat installation
+ file:
+ path: "/opt/apache-tomcat-{{ tomcat_version }}"
+ owner: "tomcat"
+ group: "tomcat"
+ state: "directory"
+ recurse: "yes"
+
+ - name: Set ownership of s3temp directory
+ file: path="{{ S3FileStorageTempDir }}" state=directory owner=tomcat group=tomcat mode=0755
+ when: S3FileStorageEnabled
+
+ - name: Create lib directory
+ file: path={{ tomcat_base }}/lib state=directory mode=0750 owner=tomcat group=tomcat
+
+ - name: Configure Tomcat server
+ template: src=server.xml dest={{ tomcat_base }}/conf/ mode=0640
+ notify:
+ - restart tomcat
+
+ - name: Ensures {{ tomcat_base }}/lib/org/apache/catalina/util/ dir exists
+ file:
+ path: "{{ tomcat_base }}/lib/org/apache/catalina/util/"
+ state: directory
+
+ - name: Copy Catalina policy for SecurityManager
+ copy:
+ src: catalina.policy
+ dest: "{{ tomcat_base }}/conf/catalina.policy"
+
+ - name: Disable Tomcat version disclosure
+ template:
+ src: ServerInfo.properties.j2
+ dest: "{{ tomcat_base }}/lib/org/apache/catalina/util/ServerInfo.properties"
+ notify:
+ - restart tomcat
+
+ - name: check to see if systemd is enabled
+ shell: "[[ `systemctl` =~ -\\.mount ]] && echo yes || echo no"
+ args:
+ executable: /bin/bash
+ register: tomcat_systemd_check
+
+ - name: check to see if upstart is enabled
+ shell: "[[ `/sbin/init --version` =~ upstart ]] && echo yes || echo no"
+ args:
+ executable: /bin/bash
+ register: tomcat_upstart_check
+
+ - name: set facts for init check
+ set_fact:
+ tomcat_using_upstart: "{{ tomcat_upstart_check.stdout | bool }}"
+ tomcat_using_systemd: "{{ tomcat_systemd_check.stdout | bool }}"
+
+ - name: Install Tomcat upstart script (Debian)
+ template: src=tomcat.conf.j2 dest=/etc/init/tomcat.conf mode=0644
+ when: ansible_os_family == 'Debian' and tomcat_using_upstart
+
+ - name: Install Tomcat systemd script
+ template: src=tomcat.service dest=/etc/systemd/system/tomcat.service mode=0644
+ when: tomcat_using_systemd
+
+ - name: Enable systemd job
+ systemd: name=tomcat.service enabled=yes daemon_reload=yes
+ when: tomcat_using_systemd
+
+ - name: Install logging.properties
+ action: template src=logging.properties dest={{ tomcat_base }}/conf/logging.properties
+ notify:
+ - restart tomcat
+
+ - name: install tomcat-jdbc.jar
+ copy:
+ src: "tomcat-jdbc-{{ tomcat_version }}.jar"
+ dest: "{{ tomcat_base }}/lib/tomcat-jdbc-{{ tomcat_version }}.jar"
+ mode: 0644
+ notify:
+ - restart tomcat
+
+ - name: cleanup default WebApps
+ file: path="{{ tomcat_base }}/webapps/{{ item.name }}" state=absent
+ with_items:
+ - { name: docs }
+ - { name: examples }
+ - { name: ROOT }
+ - { name: host-manager }
+ - { name: manager }
+ notify:
+ - restart tomcat
+
+ - name: Install logrotate script
+ template: src=tomcat.logrotate.j2 dest=/etc/logrotate.d/tomcat.logrotate mode=0644
+
+ - name: Symlink Log directory
+ file: src={{ tomcat_base }}/logs path=/var/log/tomcat state=link
+
+ - name: "Restrict access to Catalina configuration files"
+ raw: "chmod 640 {{ tomcat_dir }}/conf/*"
+
+ - name: "Restrict access to Catalina configuration directory"
+ raw: "chmod 750 {{ tomcat_dir }}/conf/"
+
+ - name: "Update owner and group of Catalina configuration directory"
+ raw: "chown root {{ tomcat_dir }}/conf/ && chgrp tomcat {{ tomcat_dir }}/conf/"
diff --git a/roles/tomcat/templates/ServerInfo.properties.j2 b/roles/tomcat/templates/ServerInfo.properties.j2
new file mode 100644
index 0000000..ed4fc2c
--- /dev/null
+++ b/roles/tomcat/templates/ServerInfo.properties.j2
@@ -0,0 +1 @@
+server.info={{ application if application is defined else 'Content Controller' }}
\ No newline at end of file
diff --git a/roles/tomcat/templates/context.xml b/roles/tomcat/templates/context.xml
new file mode 100644
index 0000000..b2f9bac
--- /dev/null
+++ b/roles/tomcat/templates/context.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ WEB-INF/web.xml
+
+
+
+
+
+
+
+
+
+
diff --git a/roles/tomcat/templates/logging.properties b/roles/tomcat/templates/logging.properties
new file mode 100755
index 0000000..5cc36ca
--- /dev/null
+++ b/roles/tomcat/templates/logging.properties
@@ -0,0 +1,56 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+handlers = 1catalina.org.apache.juli.FileHandler, 2localhost.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler
+
+#.handlers = 1catalina.org.apache.juli.FileHandler, java.util.logging.ConsoleHandler
+.handlers = 1catalina.org.apache.juli.FileHandler
+
+############################################################
+# Handler specific properties.
+# Describes specific configuration info for Handlers.
+############################################################
+
+1catalina.org.apache.juli.FileHandler.level = INFO
+1catalina.org.apache.juli.FileHandler.directory = {{ tomcat_log_path }}
+1catalina.org.apache.juli.FileHandler.prefix = catalina.
+#1catalina.org.apache.juli.FileHandler.bufferSize = -1
+
+2localhost.org.apache.juli.FileHandler.level = INFO
+2localhost.org.apache.juli.FileHandler.directory = {{ tomcat_log_path }}
+2localhost.org.apache.juli.FileHandler.prefix = localhost.
+
+java.util.logging.ConsoleHandler.level = NONE
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+
+############################################################
+# Facility specific properties.
+# Provides extra control for each logger.
+############################################################
+
+org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
+org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.FileHandler
+
+# For example, set the com.xyz.foo logger to only log SEVERE
+# messages:
+#org.apache.catalina.startup.ContextConfig.level = FINE
+#org.apache.catalina.startup.HostConfig.level = FINE
+#org.apache.catalina.session.ManagerBase.level = FINE
+#org.apache.catalina.core.AprLifecycleListener.level=FINE
+
+## messages through integration methods LogXXX are in com.rusticisoftware
+## namespace
+com.rusticisoftware.level = INFO
+net.spy.memcached.level = NONE
diff --git a/roles/tomcat/templates/server.xml b/roles/tomcat/templates/server.xml
new file mode 100755
index 0000000..9814d6d
--- /dev/null
+++ b/roles/tomcat/templates/server.xml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/roles/tomcat/templates/tomcat-users.xml.j2 b/roles/tomcat/templates/tomcat-users.xml.j2
new file mode 100644
index 0000000..011a6f5
--- /dev/null
+++ b/roles/tomcat/templates/tomcat-users.xml.j2
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/roles/tomcat/templates/tomcat.conf.j2 b/roles/tomcat/templates/tomcat.conf.j2
new file mode 100644
index 0000000..8f28015
--- /dev/null
+++ b/roles/tomcat/templates/tomcat.conf.j2
@@ -0,0 +1,33 @@
+description "Tomcat Server"
+
+ start on runlevel [2345]
+ stop on runlevel [!2345]
+ respawn
+ respawn limit 10 5
+
+ # run as non privileged user
+ # add user with this command:
+ ## adduser --system --ingroup www-data --home /opt/apache-tomcat apache-tomcat
+ # Ubuntu 12.04: (use 'exec sudo -u apache-tomcat' when using 10.04)
+ setuid tomcat
+ setgid tomcat
+
+ # adapt paths - Replace with your Paths:
+ env JAVA_HOME={{ java_home }}
+ env CATALINA_HOME={{ tomcat_base }}
+
+ # adapt java options to suit your needs:
+ env JAVA_OPTS="-Djava.awt.headless=true -Xms{{ engine_heap_size }} -Xmx{{ engine_heap_size }} -XX:+UseConcMarkSweepGC -XX:-OmitStackTraceInFastThrow"
+
+ {% if ClientName is defined %}
+ env NEW_RELIC_APP_NAME="{{ ClientName }}-{{ env }}-SCORMEngine"
+ {% else %}
+ env NEW_RELIC_APP_NAME="{{ env }}"
+ {% endif %}
+
+ exec $CATALINA_HOME/bin/catalina.sh run
+
+ # cleanup temp directory after stop
+ post-stop script
+ rm -rf $CATALINA_HOME/temp/*
+ end script
diff --git a/roles/tomcat/templates/tomcat.logrotate.j2 b/roles/tomcat/templates/tomcat.logrotate.j2
new file mode 100644
index 0000000..11d49af
--- /dev/null
+++ b/roles/tomcat/templates/tomcat.logrotate.j2
@@ -0,0 +1,9 @@
+{{ tomcat_log_path }}/catalina.out {
+ copytruncate
+ compress
+ missingok
+ notifempty
+ rotate 4
+ daily
+}
+
diff --git a/roles/tomcat/templates/tomcat.service b/roles/tomcat/templates/tomcat.service
new file mode 100644
index 0000000..b30047a
--- /dev/null
+++ b/roles/tomcat/templates/tomcat.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Apache Tomcat Web Application Container
+After=network.target
+[Service]
+Type=forking
+ExecStart={{ tomcat_base }}/bin/startup.sh
+ExecStop={{ tomcat_base }}/shutdown.sh
+Environment="JAVA_OPTS=-Xms{{ engine_heap_size }} -Xmx{{ engine_heap_size }}"
+{% if fips_java_home is defined %}
+Environment="JAVA_HOME={{ fips_java_home }}"
+{% endif %}
+User=tomcat
+Group=tomcat
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/roles/tomcat/templates/web.xml b/roles/tomcat/templates/web.xml
new file mode 100755
index 0000000..d679545
--- /dev/null
+++ b/roles/tomcat/templates/web.xml
@@ -0,0 +1,1197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ default
+ org.apache.catalina.servlets.DefaultServlet
+
+ debug
+ 0
+
+
+ listings
+ false
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ jsp
+ org.apache.jasper.servlet.JspServlet
+
+ fork
+ false
+
+
+ xpoweredBy
+ false
+
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ default
+ /
+
+
+
+
+
+
+
+ jsp
+ *.jsp
+
+
+
+ jsp
+ *.jspx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 30
+
+
+
+
+
+
+
+
+
+
+
+ abs
+ audio/x-mpeg
+
+
+ ai
+ application/postscript
+
+
+ aif
+ audio/x-aiff
+
+
+ aifc
+ audio/x-aiff
+
+
+ aiff
+ audio/x-aiff
+
+
+ aim
+ application/x-aim
+
+
+ art
+ image/x-jg
+
+
+ asf
+ video/x-ms-asf
+
+
+ asx
+ video/x-ms-asf
+
+
+ au
+ audio/basic
+
+
+ avi
+ video/x-msvideo
+
+
+ avx
+ video/x-rad-screenplay
+
+
+ bcpio
+ application/x-bcpio
+
+
+ bin
+ application/octet-stream
+
+
+ bmp
+ image/bmp
+
+
+ body
+ text/html
+
+
+ cdf
+ application/x-cdf
+
+
+ cer
+ application/x-x509-ca-cert
+
+
+ class
+ application/java
+
+
+ cpio
+ application/x-cpio
+
+
+ csh
+ application/x-csh
+
+
+ css
+ text/css
+
+
+ dib
+ image/bmp
+
+
+ doc
+ application/msword
+
+
+ dtd
+ application/xml-dtd
+
+
+ dv
+ video/x-dv
+
+
+ dvi
+ application/x-dvi
+
+
+ eps
+ application/postscript
+
+
+ etx
+ text/x-setext
+
+
+ exe
+ application/octet-stream
+
+
+ gif
+ image/gif
+
+
+ gtar
+ application/x-gtar
+
+
+ gz
+ application/x-gzip
+
+
+ hdf
+ application/x-hdf
+
+
+ hqx
+ application/mac-binhex40
+
+
+ htc
+ text/x-component
+
+
+ htm
+ text/html
+
+
+ html
+ text/html
+
+
+ hqx
+ application/mac-binhex40
+
+
+ ief
+ image/ief
+
+
+ jad
+ text/vnd.sun.j2me.app-descriptor
+
+
+ jar
+ application/java-archive
+
+
+ java
+ text/plain
+
+
+ jnlp
+ application/x-java-jnlp-file
+
+
+ jpe
+ image/jpeg
+
+
+ jpeg
+ image/jpeg
+
+
+ jpg
+ image/jpeg
+
+
+ js
+ text/javascript
+
+
+ jsf
+ text/plain
+
+
+ jspf
+ text/plain
+
+
+ kar
+ audio/x-midi
+
+
+ latex
+ application/x-latex
+
+
+ m3u
+ audio/x-mpegurl
+
+
+ mac
+ image/x-macpaint
+
+
+ man
+ application/x-troff-man
+
+
+ mathml
+ application/mathml+xml
+
+
+ me
+ application/x-troff-me
+
+
+ mid
+ audio/x-midi
+
+
+ midi
+ audio/x-midi
+
+
+ mif
+ application/x-mif
+
+
+ mov
+ video/quicktime
+
+
+ movie
+ video/x-sgi-movie
+
+
+ mp1
+ audio/x-mpeg
+
+
+ mp2
+ audio/x-mpeg
+
+
+ mp3
+ audio/x-mpeg
+
+
+ mp4
+ video/mp4
+
+
+ mpa
+ audio/x-mpeg
+
+
+ mpe
+ video/mpeg
+
+
+ mpeg
+ video/mpeg
+
+
+ mpega
+ audio/x-mpeg
+
+
+ mpg
+ video/mpeg
+
+
+ mpv2
+ video/mpeg2
+
+
+ ms
+ application/x-wais-source
+
+
+ nc
+ application/x-netcdf
+
+
+ oda
+ application/oda
+
+
+
+ odb
+ application/vnd.oasis.opendocument.database
+
+
+
+ odc
+ application/vnd.oasis.opendocument.chart
+
+
+
+ odf
+ application/vnd.oasis.opendocument.formula
+
+
+
+ odg
+ application/vnd.oasis.opendocument.graphics
+
+
+
+ odi
+ application/vnd.oasis.opendocument.image
+
+
+
+ odm
+ application/vnd.oasis.opendocument.text-master
+
+
+
+ odp
+ application/vnd.oasis.opendocument.presentation
+
+
+
+ ods
+ application/vnd.oasis.opendocument.spreadsheet
+
+
+
+ odt
+ application/vnd.oasis.opendocument.text
+
+
+ ogg
+ application/ogg
+
+
+
+ otg
+ application/vnd.oasis.opendocument.graphics-template
+
+
+
+ oth
+ application/vnd.oasis.opendocument.text-web
+
+
+
+ otp
+ application/vnd.oasis.opendocument.presentation-template
+
+
+
+ ots
+ application/vnd.oasis.opendocument.spreadsheet-template
+
+
+
+ ott
+ application/vnd.oasis.opendocument.text-template
+
+
+ pbm
+ image/x-portable-bitmap
+
+
+ pct
+ image/pict
+
+
+ pdf
+ application/pdf
+
+
+ pgm
+ image/x-portable-graymap
+
+
+ pic
+ image/pict
+
+
+ pict
+ image/pict
+
+
+ pls
+ audio/x-scpls
+
+
+ png
+ image/png
+
+
+ pnm
+ image/x-portable-anymap
+
+
+ pnt
+ image/x-macpaint
+
+
+ ppm
+ image/x-portable-pixmap
+
+
+ ppt
+ application/vnd.ms-powerpoint
+
+
+ pps
+ application/vnd.ms-powerpoint
+
+
+ ps
+ application/postscript
+
+
+ psd
+ image/x-photoshop
+
+
+ qt
+ video/quicktime
+
+
+ qti
+ image/x-quicktime
+
+
+ qtif
+ image/x-quicktime
+
+
+ ras
+ image/x-cmu-raster
+
+
+ rdf
+ application/rdf+xml
+
+
+ rgb
+ image/x-rgb
+
+
+ rm
+ application/vnd.rn-realmedia
+
+
+ roff
+ application/x-troff
+
+
+ rtf
+ application/rtf
+
+
+ rtx
+ text/richtext
+
+
+ sh
+ application/x-sh
+
+
+ shar
+ application/x-shar
+
+
+
+ smf
+ audio/x-midi
+
+
+ sit
+ application/x-stuffit
+
+
+ snd
+ audio/basic
+
+
+ src
+ application/x-wais-source
+
+
+ sv4cpio
+ application/x-sv4cpio
+
+
+ sv4crc
+ application/x-sv4crc
+
+
+ svg
+ image/svg+xml
+
+
+ svgz
+ image/svg+xml
+
+
+ swf
+ application/x-shockwave-flash
+
+
+ t
+ application/x-troff
+
+
+ tar
+ application/x-tar
+
+
+ tcl
+ application/x-tcl
+
+
+ tex
+ application/x-tex
+
+
+ texi
+ application/x-texinfo
+
+
+ texinfo
+ application/x-texinfo
+
+
+ tif
+ image/tiff
+
+
+ tiff
+ image/tiff
+
+
+ tr
+ application/x-troff
+
+
+ tsv
+ text/tab-separated-values
+
+
+ txt
+ text/plain
+
+
+ ulw
+ audio/basic
+
+
+ ustar
+ application/x-ustar
+
+
+ vtt
+ text/vtt
+
+
+ vxml
+ application/voicexml+xml
+
+
+ xbm
+ image/x-xbitmap
+
+
+ xht
+ application/xhtml+xml
+
+
+ xhtml
+ application/xhtml+xml
+
+
+ xls
+ application/vnd.ms-excel
+
+
+ xml
+ application/xml
+
+
+ xpm
+ image/x-xpixmap
+
+
+ xsl
+ application/xml
+
+
+ xslt
+ application/xslt+xml
+
+
+ xul
+ application/vnd.mozilla.xul+xml
+
+
+ xwd
+ image/x-xwindowdump
+
+
+ vsd
+ application/x-visio
+
+
+ wav
+ audio/x-wav
+
+
+
+ wbmp
+ image/vnd.wap.wbmp
+
+
+
+ wml
+ text/vnd.wap.wml
+
+
+
+ wmlc
+ application/vnd.wap.wmlc
+
+
+
+ wmls
+ text/vnd.wap.wmlscript
+
+
+
+ wmlscriptc
+ application/vnd.wap.wmlscriptc
+
+
+ wmv
+ video/x-ms-wmv
+
+
+ woff
+ application/font-woff
+
+
+ wrl
+ x-world/x-vrml
+
+
+ wspolicy
+ application/wspolicy+xml
+
+
+ Z
+ application/x-compress
+
+
+ z
+ application/x-compress
+
+
+ zip
+ application/zip
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ index.html
+ index.htm
+ index.jsp
+
+
+
+
diff --git a/roles/tomcat/templates/workers.properties b/roles/tomcat/templates/workers.properties
new file mode 100755
index 0000000..faba908
--- /dev/null
+++ b/roles/tomcat/templates/workers.properties
@@ -0,0 +1,17 @@
+workers.tomcat_home={{ tomcat_base }}
+workers.java_home={{ java_home }}
+ps=/
+
+worker.ajp13_worker.port=8080
+worker.ajp13_worker.host=localhost
+worker.ajp13_worker.type=ajp13
+worker.ajp13_worker.lbfactor=1
+worker.ajp13_worker.connect_timeout=10000
+worker.ajp13_worker.prepost_timeout=10000
+worker.ajp13_worker.socket_timeout=300
+worker.ajp13_worker.connection_pool_timeout=30
+worker.ajp13_worker.max_packet_size=16384
+worker.ajp13_worker.secret=ZpVVboYmRQuWaTeKBFXYEKdipqBTJxJHKyhXTi8QwCkepvNQXKdDtJTTYoGLGiUq
+
+worker.loadbalancer.type=lb
+worker.loadbalancer.balance_workers=ajp13_worker
diff --git a/setup.sh b/setup.sh
new file mode 100755
index 0000000..5eb5c03
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+
+# Copy templates into position and set good random passwords for things
+
+DATE=`date +%s`
+SERVERNAME=$1
+
+if [[ $# -eq 0 ]];
+ then
+ echo "Usage: setup.sh cc.example.com"
+ echo "The only argument should be the Fully Qualified DNS Name you wish to assign to Content Controller"
+ echo "This value is added to the ServerName variable in env.yml"
+ exit 1
+fi
+
+
+
+random()
+{
+ RAND=`cat /dev/urandom | env LC_CTYPE=C tr -dc 'a-zA-Z0-9!@$^_=+' | fold -w ${1:-32} | head -n 1`
+ PASSWORD="'$RAND'"
+}
+
+
+while [[ ! $PROCEED =~ "YES" ]]; do
+ echo "WARNING!!!"
+ echo "This script sets up the files in group_vars/ with good default values for passwords and keys"
+ echo "and copies the templates into place."
+ echo "This script will overwrite all of the .yml files in the group_vars/ folder."
+ echo "Unless you're configuring Content Controller for the 1st time, this is probably not what you want."
+ echo "Please type YES to proceed, or Ctrl-c to quit"
+ read PROCEED
+done
+
+if [ ! -e group_vars/keypair.yml ]
+then
+ echo "Your friends at Rustici should have provided you with a license keypair that you'll need in order to install Content Controller."
+ echo "Please enter them here: (or leave these blank and just drop in your license file later.)"
+ read -p "Please enter your download key id (AKIAxxxxxxxxxxxx): " DKEY
+ read -p "Please enter your download secret: " SKEY
+ KEYPAIR="overwrite"
+fi
+
+echo $KEYPAIR
+
+cd group_vars
+
+if [ -e env.yml ]
+then
+ cp env.yml env.yml.$DATE
+fi
+
+if [ -e content_controller.yml ]
+then
+ cp content_controller.yml content_controller.yml.$DATE
+fi
+
+if [ -e engine_java.yml ]
+then
+ cp engine_java.yml engine_java.yml.$DATE
+fi
+
+if [ -e cloudfront.yml ]
+then
+ cp cloudfront.yml cloudfront.yml.$DATE
+fi
+
+if [ -e s3.yml ]
+then
+ cp s3.yml s3.yml.$DATE
+fi
+
+cp env.yml.template env.yml
+cp content_controller.yml.template content_controller.yml
+cp engine_java.yml.template engine_java.yml
+cp s3.yml.template s3.yml
+cp cloudfront.yml.template cloudfront.yml
+cp aws.yml.template aws.yml
+
+
+# Setup env.yml
+sed -i.bak "s/\(^ServerName\:\).*/\1 $SERVERNAME/" env.yml
+random 16; sed -i.bak "s/\(^tomcat_password\:\).*/\1 $PASSWORD/" env.yml
+random 16; sed -i.bak "s/\(^mysql_root_password\:\).*/\1 $PASSWORD/" env.yml
+
+# Setup Content Controller.yml
+random 16; sed -i.bak "s/\(^cc_db_password\:\).*/\1 $PASSWORD/" content_controller.yml
+random 16; sed -i.bak "s/\(^engine_password\:\).*/\1 $PASSWORD/" content_controller.yml
+random 64; sed -i.bak "s/\(^secret_key\:\).*/\1 $PASSWORD/" content_controller.yml
+random 16; sed -i.bak "s/\(^cc_user_password\:\).*/\1 $PASSWORD/" content_controller.yml
+random 64; sed -i.bak "s/\(^deep_linking_secret\:\).*/\1 $PASSWORD/" content_controller.yml
+random 64; sed -i.bak "s/\(^webhooks_auth_secret\:\).*/\1 $PASSWORD/" content_controller.yml
+
+# Setup engine-java.yml
+random 16; sed -i.bak "s/\(^engine_db_password\:\).*/\1 $PASSWORD/" engine_java.yml
+
+# Setup keypair.yml
+if [[ "$KEYPAIR" == "overwrite" ]]; then
+ cp keypair.yml.template keypair.yml
+ sed -i.bak "s/\(^s3_download_key\:\).*/\1 $DKEY/" keypair.yml
+ sed -i.bak "s/\(^s3_download_secret\:\).*/\1 $SKEY/" keypair.yml
+fi
+
+# Remove the extra .bak files that we had to generate since we're in compatibility mode with sed for OSX + Linux
+rm engine_java.yml.bak
+rm content_controller.yml.bak
+rm env.yml.bak
+
+echo "Done - I have copied over the templates and set new random passwords in the config files in group_vars/"
+
+# Prevent further execution unless it's intentional
+ME=`basename $0`
+chmod 0600 ../$ME