You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2018/09/24 22:05:24 UTC

[GitHub] JBevillC closed pull request #2870: Box

JBevillC closed pull request #2870: Box
URL: https://github.com/apache/trafficcontrol/pull/2870
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/infrastructure/cdn-in-a-box/Makefile b/infrastructure/cdn-in-a-box/Makefile
new file mode 100644
index 000000000..c553be4aa
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/Makefile
@@ -0,0 +1,95 @@
+# 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.
+
+############################################################
+# Dockerfile to build Edge-Tier Cache container images for
+# Apache Traffic Control
+# Based on CentOS 7.2
+############################################################
+
+# Check for proper invocation
+PWD := $(strip $(lastword $(patsubst %/,%,$(notdir $(shell pwd)))))
+makefile_dir := $(strip $(notdir $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))))
+
+ifneq ($(PWD),$(makefile_dir))
+$(error This makefile MUST be run from within its directory)
+endif
+
+BUILD_NUMBER := $(shell git rev-list HEAD 2>/dev/null | wc -l).$(shell git rev-parse --short=8 HEAD)
+TC_VERSION := $(shell cat "../../VERSION")
+TOMCAT_VERSION := $(shell grep 'export TOMCAT_VERSION=' ../../traffic_router/build/build_rpm.sh  | cut -d '=' -f 2)
+TOMCAT_RELEASE := $(shell grep 'export TOMCAT_RELEASE=' ../../traffic_router/build/build_rpm.sh  | cut -d '=' -f 2)
+
+ifeq ($(shell which rpm 2>/dev/null),)
+RHEL_VERSION :=el7
+$(info Couldn't find 'rpm' binary, building for RHEL version $(RHEL_VERSION))
+else
+RHEL_VERSION :=el$(shell rpm -q --qf "%{VERSION}" $(shell rpm -q --whatprovides redhat-release))
+endif
+
+SPECIAL_SAUCE := $(TC_VERSION)-$(BUILD_NUMBER).$(RHEL_VERSION).x86_64.rpm
+SPECIAL_SEASONING := $(TOMCAT_VERSION).$(TOMCAT_RELEASE)-$(BUILD_NUMBER).$(RHEL_VERSION).x86_64.rpm
+
+TO_SOURCE := $(wildcard ../../traffic_ops/**/*)
+ORT_SOURCE:= $(wildcard ../../traffic_ops/bin/*)
+TO_SOURCE += $(wildcard ../../traffic_ops_db/**/*)
+TM_SOURCE := $(wildcard ../../traffic_monitor/**/*)
+TP_SOURCE := $(wildcard ../../traffic_portal/**/*)
+TR_SOURCE := $(wildcard ../../traffic_router/**/*)
+
+.PHONY: clean very-clean all nearly-all
+
+# Default target; builds all pre-requisite rpms from source trees
+all: traffic_monitor/traffic_monitor.rpm traffic_portal/traffic_portal.rpm traffic_ops/traffic_ops.rpm mid/traffic_ops_ort.rpm edge/traffic_ops_ort.rpm traffic_router/traffic_router.rpm traffic_router/tomcat.rpm
+
+# Actual output rpm recipies
+traffic_monitor/traffic_monitor.rpm: ../../dist/traffic_monitor-$(SPECIAL_SAUCE)
+	cp -f $? $@
+traffic_ops/traffic_ops.rpm: ../../dist/traffic_ops-$(SPECIAL_SAUCE)
+	cp -f $? $@
+mid/traffic_ops_ort.rpm edge/traffic_ops_ort.rpm: ../../dist/traffic_ops_ort-$(SPECIAL_SAUCE)
+	cp -f $? $@
+traffic_portal/traffic_portal.rpm: ../../dist/traffic_portal-$(SPECIAL_SAUCE)
+	cp -f $? $@
+traffic_router/traffic_router.rpm: ../../dist/traffic_router-$(SPECIAL_SAUCE)
+	cp -f $? $@
+traffic_router/tomcat.rpm: ../../dist/tomcat-$(SPECIAL_SEASONING)
+	cp -f $? $@
+
+# Dist rpms
+../../dist/traffic_monitor-$(SPECIAL_SAUCE): $(TM_SOURCE)
+	sudo ../../pkg -v traffic_monitor_build
+
+../../dist/traffic_ops-$(SPECIAL_SAUCE): $(TO_SOURCE)
+	sudo ../../pkg -v traffic_ops_build
+
+../../dist/traffic_ops_ort-$(SPECIAL_SAUCE): $(ORT_SOURCE)
+	sudo ../../pkg -v traffic_ops_build
+
+../../dist/traffic_portal-$(SPECIAL_SAUCE): $(TP_SOURCE)
+	sudo ../../pkg -v traffic_portal_build
+
+../../dist/traffic_router-$(SPECIAL_SAUCE): $(TR_SOURCE)
+	sudo ../../pkg -v traffic_router_build
+
+clean:
+	$(RM) traffic_ops/traffic_ops.rpm traffic_monitor/traffic_monitor.rpm traffic_portal/traffic_portal.rpm traffic_router/traffic_router.rpm traffic_router/tomcat.rpm edge/traffic_ops_ort.rpm mid/traffic_ops_ort.rpm
+
+very-clean: clean
+	$(warning This will destroy ALL OUTPUT RPMS IN 'dist'. Please be sure this is what you want)
+	sleep 2 # Give users a sceond to cancel
+	$(RM) ../../dist/*
diff --git a/infrastructure/cdn-in-a-box/README.md b/infrastructure/cdn-in-a-box/README.md
index 6b3ac0d47..207de6e72 100644
--- a/infrastructure/cdn-in-a-box/README.md
+++ b/infrastructure/cdn-in-a-box/README.md
@@ -38,10 +38,12 @@ via the distribution's package manager under the names `docker-ce` and
 `docker-compose`, respectively (e.g. `sudo yum install docker-ce`).
 
 Each container (except the origin) requires an `.rpm` file to install the Traffic Control
-component for which it is responsible. You can either download these `*.rpm` files or
-create them yourself by using the [`pkg`](../../pkg) script at the root of the
-repository. Copy the `*.rpm`s without any version/architecture information to their
-respective component directories, such that their filenames are as follows:
+component for which it is responsible. You can download these `*.rpm` files from an archive
+(e.g. under "Releases"), use the provided [Makefile](./Makefile) to generate them (simply
+type `make` while in the `cdn-in-a-box` directory) or create them yourself by using the
+[`pkg`](../../pkg) script at the root of the repository. If you choose the latter, copy
+the `*.rpm`s without any version/architecture information to their respective component
+directories, such that their filenames are as follows:
 
 * `edge/traffic_ops_ort.rpm`
 * `mid/traffic_ops_ort.rpm`
@@ -137,3 +139,12 @@ a very simple page sporting the Traffic Control logo.
 
 The process creates containers for each component with ports exposed on the host.  The
 following should be available once the system is running:
+
+
+## Common Pitfalls
+
+> Everything's "waiting for Traffic Ops" forever and nothing seems to be working - what do?
+> If you scroll back through the output ( or use `docker compose logs trafficops-perl | grep "User defined signal 2"` ) and see a line that says something like `/run.sh: line 79: 118 User defined signal 2 $TO_DIR/local/bin/hypnotoad script/cdn` then you've hit a mysterious known error. We don't know what this is or why it happens, but your best bet is to send up a quick prayer and restart the stack.
+
+> I'm seeing a failure to open a socket and/or set a socket option
+> Try disabling SELinux or setting it to 'permissive'. SELinux hates letting containers bind to certain ports. You can also try re-labeling the `docker` executable if you feel comfortable.
diff --git a/infrastructure/cdn-in-a-box/cache/Dockerfile b/infrastructure/cdn-in-a-box/cache/Dockerfile
new file mode 100644
index 000000000..26c4fef6a
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/cache/Dockerfile
@@ -0,0 +1,52 @@
+# 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.
+
+############################################################
+# Dockerfile to build Edge-Tier Cache container images for
+# Apache Traffic Control
+# Based on CentOS 7.2
+############################################################
+
+FROM centos:7
+
+EXPOSE 80
+
+ADD https://ci.trafficserver.apache.org/RPMS/CentOS7/trafficserver-7.1.4-1.el7.x86_64.rpm /trafficserver.rpm
+ADD https://ci.trafficserver.apache.org/RPMS/CentOS7/trafficserver-devel-7.1.4-1.el7.x86_64.rpm /trafficserver-devel.rpm
+
+RUN yum install -y kyotocabinet-libs epel-release initscripts iproute net-tools nmap-ncat gettext autoconf automake libtool gcc-c++ cronie glibc-devel openssl-devel
+RUN yum install -y /trafficserver.rpm /trafficserver-devel.rpm jq python34-psutil python34-typing python34-setuptools python34-pip && yum clean all
+RUN pip3 install --upgrade pip && pip3 install requests urllib3 distro
+
+ADD traffic_server/plugins/astats_over_http/astats_over_http.c traffic_server/plugins/astats_over_http/Makefile.am /
+
+RUN tsxs -v -c astats_over_http.c -o astats_over_http.so
+RUN mkdir -p /usr/libexec/trafficserver && tsxs -v -o astats_over_http.so -i
+
+RUN yum remove -y gcc-c++ glibc-devel autoconf automake libtool && rm -f /astats_over_http.c /Makefile.am
+
+# You need to do this because the RPM in the ATS archives is just all kinds of messed-up
+RUN chmod 755 /usr/lib64/trafficserver /etc/trafficserver/body_factory /etc/trafficserver/body_factory/default
+RUN mkdir -p /var/trafficserver/cache /opt/ort && chown -R ats:ats /etc/trafficserver/ /var/trafficserver/ /opt/ort /usr/lib64/trafficserver/
+RUN setcap CAP_NET_BIND_SERVICE=+eip /bin/traffic_server && setcap CAP_NET_BIND_SERVICE=+eip /bin/traffic_manager && setcap CAP_NET_BIND_SERVICE=+eip /bin/trafficserver && setcap CAP_NET_BIND_SERVICE=+eip /bin/traffic_cop
+
+ADD infrastructure/cdn-in-a-box/edge/traffic_ops_ort.py /opt/ort/
+ADD infrastructure/cdn-in-a-box/cache/crontab /etc/cron.d/traffic_ops_ort-cron-template
+
+RUN touch /var/log/ort.log
+
+CMD exit
diff --git a/infrastructure/cdn-in-a-box/cache/crontab b/infrastructure/cdn-in-a-box/cache/crontab
new file mode 100644
index 000000000..25eedbc31
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/cache/crontab
@@ -0,0 +1 @@
+*/1 * * * * /opt/ort/traffic_ops_ort.py SYNCDS ALL https://$TO_FQDN $TO_ADMIN_USER:$TO_ADMIN_PASSWORD >> /var/log/ort.log 2>> /var/log/ort.log
diff --git a/infrastructure/cdn-in-a-box/dns/Dockerfile b/infrastructure/cdn-in-a-box/dns/Dockerfile
new file mode 100644
index 000000000..fdea17dd5
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/dns/Dockerfile
@@ -0,0 +1,45 @@
+# 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.
+FROM ubuntu:trusty-20170817
+
+ENV BIND_USER=bind \
+    BIND_VERSION=1:9.9.5 \
+    DATA_DIR=/data
+
+RUN echo 'APT::Install-Recommends 0;' >> /etc/apt/apt.conf.d/01norecommends \
+ && echo 'APT::Install-Suggests 0;' >> /etc/apt/apt.conf.d/01norecommends \
+ && apt-get update \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y vim.tiny wget net-tools sudo net-tools ca-certificates unzip apt-transport-https \
+ && rm -rf /var/lib/apt/lists/* && rm -rf /etc/apt/apt.conf.d/docker-gzip-indexes \
+ && apt-get update \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y bind9=${BIND_VERSION}* bind9-host=${BIND_VERSION}* dnsutils \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY dns/entrypoint.sh /sbin/entrypoint.sh
+COPY dns/named.conf.local /etc/bind
+COPY dns/named.conf.options /etc/bind
+COPY dns/zone.ciab.test /etc/bind
+COPY dns/zone.ip4.arpa /etc/bind
+COPY dns/zone.ip6.arpa /etc/bind
+COPY traffic_ops/to-access.sh /
+COPY enroller/server_template.json /
+
+RUN chmod 755 /sbin/entrypoint.sh
+
+EXPOSE 53/udp 53/tcp
+ENTRYPOINT ["/sbin/entrypoint.sh"]
+CMD ["/usr/sbin/named"]
diff --git a/infrastructure/cdn-in-a-box/dns/container-resolv.conf b/infrastructure/cdn-in-a-box/dns/container-resolv.conf
new file mode 100644
index 000000000..2c33b328f
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/dns/container-resolv.conf
@@ -0,0 +1,19 @@
+# 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.
+domain ciab.test
+search infra.ciab.test ciab.test
+nameserver 172.16.239.254
diff --git a/infrastructure/cdn-in-a-box/dns/entrypoint.sh b/infrastructure/cdn-in-a-box/dns/entrypoint.sh
new file mode 100644
index 000000000..804c8a10c
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/dns/entrypoint.sh
@@ -0,0 +1,72 @@
+#!/bin/bash
+# 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.
+#
+set -e
+
+BIND_DATA_DIR=${DATA_DIR}/bind
+
+create_bind_data_dir() {
+  mkdir -p ${BIND_DATA_DIR}
+
+  # populate default bind configuration if it does not exist
+  if [ ! -d ${BIND_DATA_DIR}/etc ]; then
+    mv /etc/bind ${BIND_DATA_DIR}/etc
+  fi
+  rm -rf /etc/bind
+  ln -sf ${BIND_DATA_DIR}/etc /etc/bind
+  chmod -R 0775 ${BIND_DATA_DIR}
+  chown -R ${BIND_USER}:${BIND_USER} ${BIND_DATA_DIR}
+
+  if [ ! -d ${BIND_DATA_DIR}/lib ]; then
+    mkdir -p ${BIND_DATA_DIR}/lib
+    chown ${BIND_USER}:${BIND_USER} ${BIND_DATA_DIR}/lib
+  fi
+  rm -rf /var/lib/bind
+  ln -sf ${BIND_DATA_DIR}/lib /var/lib/bind
+}
+
+create_pid_dir() {
+  mkdir -m 0775 -p /var/run/named
+  chown root:${BIND_USER} /var/run/named
+}
+
+create_bind_cache_dir() {
+  mkdir -m 0775 -p /var/cache/bind
+  chown root:${BIND_USER} /var/cache/bind
+}
+
+create_pid_dir
+create_bind_data_dir
+create_bind_cache_dir
+
+# allow arguments to be passed to named
+if [[ ${1:0:1} = '-' ]]; then
+  EXTRA_ARGS="$@"
+  set --
+elif [[ ${1} == named || ${1} == $(which named) ]]; then
+  EXTRA_ARGS="${@:2}"
+  set --
+fi
+
+# default behaviour is to launch named
+if [[ -z ${1} ]]; then
+  echo "Starting named..."
+  exec $(which named) -u ${BIND_USER} -g ${EXTRA_ARGS} 
+else
+  exec "$@"
+fi
diff --git a/infrastructure/cdn-in-a-box/dns/named.conf.local b/infrastructure/cdn-in-a-box/dns/named.conf.local
new file mode 100644
index 000000000..46334cf06
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/dns/named.conf.local
@@ -0,0 +1,33 @@
+# 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.
+
+zone "ciab.test" {
+  type master;
+  file "/etc/bind/zone.ciab.test";
+  forwarders {};
+};
+
+zone "239.16.172.in-addr.arpa" IN {
+  type master;
+  file "/etc/bind/zone.ip4.arpa";
+  forwarders {};
+};
+zone "0.0.0.0.0.0.0.0.0.0.0.0.0.8.0.0.0.0.0.0.1.0.0.4.9.1.0.c.f.ip6.arpa" IN {
+  type master;
+  file "/etc/bind/zone.ip6.arpa";
+  forwarders {};
+};
diff --git a/infrastructure/cdn-in-a-box/dns/named.conf.options b/infrastructure/cdn-in-a-box/dns/named.conf.options
new file mode 100644
index 000000000..2f76f3369
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/dns/named.conf.options
@@ -0,0 +1,32 @@
+# 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.
+
+options {
+  directory "/var/cache/bind";
+
+  forwarders {
+    127.0.0.11;
+  };
+
+  dnssec-validation no;
+
+  auth-nxdomain no;    # conform to RFC1035
+  listen-on-v6 { any; };
+  allow-query { any; };
+  allow-recursion { any; };
+  allow-query-cache { any; };
+};
diff --git a/infrastructure/cdn-in-a-box/dns/zone.ciab.test b/infrastructure/cdn-in-a-box/dns/zone.ciab.test
new file mode 100644
index 000000000..e5a07ec19
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/dns/zone.ciab.test
@@ -0,0 +1,74 @@
+; 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.
+
+$ORIGIN ciab.test.
+$TTL 30 
+@   IN SOA  dns.infra.ciab.test. hostmaster.ciab.test. (
+        0000000001; serial
+        7200      ; refresh (2 hours)
+        3600      ; retry (1 hour)
+        604800    ; expire (1 week)
+        38400     ; minimum (10 hours 40 minutes)
+        )
+									IN NS   dns.infra.ciab.test.
+
+$ORIGIN infra.ciab.test.
+
+gw                IN A    172.16.239.1
+gw								IN AAAA fc01:9400:1000:8::1
+
+db                IN A    172.16.239.10
+db								IN AAAA fc01:9400:1000:8::10
+
+trafficops        IN A    172.16.239.20
+trafficops        IN AAAA fc01:9400:1000:8::20
+
+trafficops-perl   IN A    172.16.239.21
+trafficops-perl   IN AAAA fc01:9400:1000:8::21
+
+trafficportal     IN A    172.16.239.30
+trafficportal     IN AAAA fc01:9400:1000:8::30
+
+trafficmonitor    IN A    172.16.239.40
+trafficmonitor    IN AAAA fc01:9400:1000:8::40
+
+trafficvault      IN A    172.16.239.50
+trafficvault      IN AAAA fc01:9400:1000:8::50
+
+trafficrouter     IN A    172.16.239.60
+trafficrouter     IN AAAA fc01:9400:1000:8::60
+
+edge              IN A    172.16.239.100
+edge              IN AAAA fc01:9400:1000:8::100
+
+mid               IN A    172.16.239.120
+mid               IN AAAA fc01:9400:1000:8::120
+
+origin            IN A    172.16.239.140
+origin            IN AAAA fc01:9400:1000:8::140
+
+enroller          IN A    172.16.239.200
+enroller          IN AAAA fc01:9400:1000:8::200
+
+client            IN A    172.16.239.250
+client            IN AAAA fc01:9400:1000:8::250
+
+dns               IN A    172.16.239.254
+dns               IN AAAA fc01:9400:1000:8::254
+
+$ORIGIN mycdn.ciab.test.
+@                 NS   trafficrouter.infra.ciab.test.
diff --git a/infrastructure/cdn-in-a-box/dns/zone.ip4.arpa b/infrastructure/cdn-in-a-box/dns/zone.ip4.arpa
new file mode 100644
index 000000000..d57f25301
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/dns/zone.ip4.arpa
@@ -0,0 +1,39 @@
+; 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.
+$ORIGIN 239.16.172.in-addr.arpa.
+$TTL 30 ; 30 seconds
+@   IN SOA  dns.infra.ciab.test. admin.infra.ciab.test. (
+        0000000001 ; serial
+        7200       ; refresh
+        3600       ; retry
+        604800     ; expire
+        38400      ; minimum
+)
+              IN NS    dns.infra.ciab.test.
+1             IN PTR   gw.infra.ciab.test.
+10            IN PTR   db.infra.ciab.test.
+20            IN PTR   trafficops.infra.ciab.test.
+21            IN PTR   trafficops-perl.infra.ciab.test.
+30            IN PTR   trafficportal.infra.ciab.test.
+40            IN PTR   trafficmonitor.infra.ciab.test.
+50            IN PTR   trafficvault.infra.ciab.test.
+60            IN PTR   trafficrouter.infra.ciab.test.
+100           IN PTR   edge.infra.ciab.test.
+120           IN PTR   mid.infra.ciab.test.
+140           IN PTR   origin.infra.ciab.test.
+200           IN PTR   enroller.infra.ciab.test.
+254           IN PTR   dns.infra.ciab.test.
diff --git a/infrastructure/cdn-in-a-box/dns/zone.ip6.arpa b/infrastructure/cdn-in-a-box/dns/zone.ip6.arpa
new file mode 100644
index 000000000..cda9b7369
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/dns/zone.ip6.arpa
@@ -0,0 +1,40 @@
+; 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.
+$ORIGIN 0.0.0.0.0.0.0.0.0.0.0.0.0.8.0.0.0.0.0.0.1.0.0.4.9.1.0.c.f.ip6.arpa.
+$TTL 30 ; 30 seconds
+@   IN SOA  dns.infra.ciab.test. admin.infra.ciab.test. (
+        0000000001 ; serial
+        7200       ; refresh
+        3600       ; retry
+        604800     ; expire
+        38400      ; minimum
+)
+              IN NS    dns.infra.ciab.test.
+1.0.0         IN PTR   gw.infra.ciab.test.
+0.1.0         IN PTR   db.infra.ciab.test.
+0.2.0         IN PTR   trafficops.infra.ciab.test.
+0.2.1         IN PTR   trafficops-perl.infra.ciab.test.
+0.3.0         IN PTR   trafficportal.infra.ciab.test.
+0.4.0         IN PTR   trafficmonitor.infra.ciab.test.
+0.5.0         IN PTR   trafficvault.infra.ciab.test.
+0.6.0         IN PTR   trafficrouter.infra.ciab.test.
+0.0.1         IN PTR   edge.infra.ciab.test.
+0.2.1         IN PTR   mid.infra.ciab.test.
+0.4.1         IN PTR   origin.infra.ciab.test.
+0.0.2         IN PTR   enroller.infra.ciab.test.
+0.5.2         IN PTR   client.infra.ciab.test.
+4.5.2         IN PTR   dns.infra.ciab.test.
diff --git a/infrastructure/cdn-in-a-box/docker-compose.yml b/infrastructure/cdn-in-a-box/docker-compose.yml
index 1da6c77cb..3e424f9bf 100644
--- a/infrastructure/cdn-in-a-box/docker-compose.yml
+++ b/infrastructure/cdn-in-a-box/docker-compose.yml
@@ -40,7 +40,7 @@ networks:
     ipam:
       driver: default
       config:
-        - subnet: 172.13.239.0/24
+        - subnet: 172.16.239.0/24
         - subnet: "fc01:9400:1000:8::/64"
 
 services:
@@ -51,11 +51,17 @@ services:
       context: traffic_ops
       dockerfile: Dockerfile-db
     hostname: db
-    domainname: cdn.local
+    domainname: infra.ciab.test
+    depends_on:
+      - dns
     networks:
-      - tcnet
+      tcnet:
+        ipv4_address: 172.16.239.10
+        ipv6_address: "fc01:9400:1000:8::10"
     volumes:
-      - ./traffic_ops/data:/var/lib/postgresql/data
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - /var/lib/postgresql/data
+      - shared:/shared
     env_file:
       - variables.env
     # TODO: change to expose: "5432" to limit to containers
@@ -71,16 +77,22 @@ services:
       dockerfile: traffic_ops/Dockerfile-go
     depends_on:
       - db
+      - enroller
       - trafficops-perl
-    domainname: cdn.local
+    domainname: infra.ciab.test
     env_file:
       - variables.env
     hostname: trafficops
     image: trafficops-go
     networks:
-      - tcnet
+      tcnet:
+        ipv4_address: 172.16.239.20
+        ipv6_address: "fc01:9400:1000:8::20"
     ports:
-      - "6443:6443"
+      - "6443:443"
+    volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - shared:/shared
 
   # trafficops-perl runs the legacy Traffic Ops in Perl using the Mojolicious framework.  This remains
   # in place until all API endpoints have been rewritten in Go
@@ -92,35 +104,47 @@ services:
         TRAFFIC_OPS_RPM: traffic_ops/traffic_ops.rpm
     depends_on:
       - db
-    domainname: cdn.local
+      - enroller
+    domainname: infra.ciab.test
     env_file:
       - variables.env
     hostname: trafficops-perl
     image: trafficops-perl
     networks:
-      - tcnet
-    depends_on:
-      - db
+      tcnet:
+        ipv4_address: 172.16.239.21
+        ipv6_address: "fc01:9400:1000:8::21"
     # TODO: change to expose: "60443" to limit to containers
     ports:
-      - "60443:60443"
+      - "60443:443"
+    volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - shared:/shared
 
   # trafficportal defines the web interface for Traffic Ops.  It uses only the API exposed by Traffic Ops
   # and passes any /api/... routes directly to Traffic Ops
   trafficportal:
     build:
-      context: traffic_portal
+      context: .
+      dockerfile: traffic_portal/Dockerfile
       args:
-        TRAFFIC_PORTAL_RPM: traffic_portal.rpm
-    domainname: cdn.local
+        TRAFFIC_PORTAL_RPM: traffic_portal/traffic_portal.rpm
+    depends_on:
+      - enroller
+    domainname: infra.ciab.test
     env_file:
       - variables.env
     hostname: trafficportal
     image: trafficportal
     networks:
-      - tcnet
+      tcnet:
+        ipv4_address: 172.16.239.30
+        ipv6_address: "fc01:9400:1000:8::30"
     ports:
       - "443:443"
+    volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - shared:/shared
 
   # trafficmonitor is an HTTP service that monitors the caches in a CDN for a variety of metrics
   trafficmonitor:
@@ -129,92 +153,190 @@ services:
       dockerfile: traffic_monitor/Dockerfile
       args:
         TRAFFIC_MONITOR_RPM: traffic_monitor/traffic_monitor.rpm
-    domainname: cdn.local
+    depends_on:
+      - enroller
+    volumes:
+      - shared:/shared
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+    domainname: infra.ciab.test
     env_file:
       - variables.env
     hostname: trafficmonitor
     image: trafficmonitor
     networks:
-      - tcnet
+      tcnet:
+        ipv4_address: 172.16.239.40
+        ipv6_address: "fc01:9400:1000:8::40"
     ports:
       - "80:80"
 
+  # trafficrouter routes clients to the most optimal cache
+  trafficrouter:
+    build:
+      context: .
+      dockerfile: traffic_router/Dockerfile
+      args:
+        TRAFFIC_ROUTER_RPM: traffic_router/traffic_router.rpm
+        TOMCAT_RPM: traffic_router/tomcat.rpm
+    depends_on:
+      - enroller
+    domainname: infra.ciab.test
+    env_file:
+      - variables.env
+    hostname: trafficrouter
+    networks:
+      tcnet:
+        ipv4_address: 172.16.239.60
+        ipv6_address: "fc01:9400:1000:8::60"
+    ports:
+      - "3053:53"
+      - "3080:80"
+      - "3443:443"
+      - "3333:3333"
+    volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - shared:/shared
+
   # trafficvault runs a riak container to store private keys
   trafficvault:
-    image: basho/riak-kv
+    build:
+      context: .
+      dockerfile: traffic_vault/Dockerfile
+    depends_on:
+      - enroller
     ports:
       - "8087:8087"
       - "8098:8098"
     environment:
-      - CLUSTER_NAME=riakkv
+      - CLUSTER_NAME=trafficvault
     labels:
       - "com.basho.riak.cluster.name=trafficvault"
     volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
       - schemas:/etc/riak/schemas
-    domainname: cdn.local
+      - shared:/shared
+    domainname: infra.ciab.test
     env_file:
       - variables.env
     hostname: trafficvault
     networks:
-      - tcnet
-    ports:
-      - "8010:80"
+      tcnet:
+        ipv4_address: 172.16.239.50
+        ipv6_address: "fc01:9400:1000:8::50"
 
   # Apache Traffic Server (ATS) caches defined here
+  # base image from which all other caches inherit (builds and installs ATS+plugins)
+  tccache:
+    build:
+      context: ../..
+      dockerfile: infrastructure/cdn-in-a-box/cache/Dockerfile
+    image: tccache:latest
+
   # edge cache
   edge:
+    privileged: True
     build:
       context: .
       dockerfile: edge/Dockerfile
-    domainname: cdn.local
+    depends_on:
+      - enroller
+      - tccache
+    domainname: infra.ciab.test
     env_file:
       - variables.env
     hostname: edge
     networks:
-      - tcnet
+      tcnet:
+        ipv4_address: 172.16.239.100
+        ipv6_address: "fc01:9400:1000:8::100"
     ports:
       - "9000:80"
+    volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - shared:/shared
 
   # mid cache
   mid:
+    privileged: True
     build:
       context: .
       dockerfile: mid/Dockerfile
-    domainname: cdn.local
+    depends_on:
+      - enroller
+      - tccache
+    domainname: infra.ciab.test
     env_file:
       - variables.env
     hostname: mid
     networks:
-      - tcnet
+      tcnet:
+        ipv4_address: 172.16.239.120
+        ipv6_address: "fc01:9400:1000:8::120"
     ports:
       - "9100:80"
+    volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - shared:/shared
 
   # origin provides the content to be distributed through the CDN caches
   origin:
     build:
-      context: origin
-    domainname: cdn.local
+      context: .
+      dockerfile: origin/Dockerfile
+    depends_on:
+      - enroller
+    domainname: infra.ciab.test
+    env_file:
+      - variables.env
     hostname: origin
     networks:
-      - tcnet
+      tcnet:
+        ipv4_address: 172.16.239.140
+        ipv6_address: "fc01:9400:1000:8::140"
     ports:
       - "9200:80"
+    volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - shared:/shared
 
   # enroller provides a way to register component instances with traffic_ops
   enroller:
     build:
       context: ../..
       dockerfile: infrastructure/cdn-in-a-box/enroller/Dockerfile
+    depends_on:
+      - dns
     env_file:
       - variables.env
     hostname: enroller
     networks:
-      - tcnet
+      tcnet:
+        ipv4_address: 172.16.239.200
+        ipv6_address: "fc01:9400:1000:8::200"
+    volumes:
+      - ./dns/container-resolv.conf:/etc/resolv.conf
+      - shared:/shared
+
+  # Bind9 DNS services work in combination with the traffic router to route clients to the optimal cache
+  dns:
+    build:
+      context: .
+      dockerfile: dns/Dockerfile
+    env_file:
+      - variables.env
+    volumes:
+      - shared:/shared
+    hostname: dns
+    domainname: infra.ciab.test
+    networks:
+      tcnet:
+        ipv4_address: 172.16.239.254
+        ipv6_address: "fc01:9400:1000:8::254"
     ports:
-      - "7443:443"
-    volumes:  
-      - /var/run/docker.sock:/var/run/docker.sock
-      
+      - "9353:53"
+
 volumes:
   schemas:
     external: false
+  shared:
+    external: false
diff --git a/infrastructure/cdn-in-a-box/edge/Dockerfile b/infrastructure/cdn-in-a-box/edge/Dockerfile
index 7484de457..c1b175d59 100644
--- a/infrastructure/cdn-in-a-box/edge/Dockerfile
+++ b/infrastructure/cdn-in-a-box/edge/Dockerfile
@@ -21,18 +21,8 @@
 # Based on CentOS 7.2
 ############################################################
 
-FROM centos:7
+FROM tccache:latest
 
-ARG RPM=https://ci.trafficserver.apache.org/RPMS/CentOS7/trafficserver-7.1.2-4.el7.centos.x86_64.rpm
-ADD $RPM /trafficserver.rpm
-ADD edge/traffic_ops_ort.rpm /
+ADD edge/run.sh traffic_ops/to-access.sh enroller/server_template.json /
 
-RUN yum install -y /trafficserver.rpm iproute net-tools nmap-ncat epel-release
-RUN yum install -y jq && yum clean all
-
-RUN mkdir -p /var/trafficserver/cache /opt/ort && chown -R ats:ats /etc/trafficserver/ /var/trafficserver/ /opt/ort
-ADD edge/parameters.json edge/profile.json edge/server.json edge/setup.sh traffic_ops/to-access.sh /
-
-EXPOSE 80
-
-CMD /setup.sh
+CMD /run.sh
diff --git a/infrastructure/cdn-in-a-box/edge/parameters.json b/infrastructure/cdn-in-a-box/edge/parameters.json
index 480b60bf1..29cfbffd7 100644
--- a/infrastructure/cdn-in-a-box/edge/parameters.json
+++ b/infrastructure/cdn-in-a-box/edge/parameters.json
@@ -6,7 +6,7 @@
 		"secure": 0
 	},
 	{
-		"value": "STRING /etc/trafficserver",
+		"value": "STRING /usr/etc/trafficserver",
 		"name": "CONFIG proxy.config.config_dir",
 		"configFile": "records.config",
 		"secure": 0
@@ -1014,49 +1014,49 @@
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver/",
+		"value": "/usr/etc/trafficserver/",
 		"name": "location",
 		"configFile": "cache.config",
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver/",
+		"value": "/usr/etc/trafficserver/",
 		"name": "location",
 		"configFile": "hosting.config",
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver/",
+		"value": "/usr/etc/trafficserver/",
 		"name": "location",
 		"configFile": "parent.config",
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver/",
+		"value": "/usr/etc/trafficserver/",
 		"name": "location",
 		"configFile": "plugin.config",
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver/",
+		"value": "/usr/etc/trafficserver/",
 		"name": "location",
 		"configFile": "records.config",
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver/",
+		"value": "/usr/etc/trafficserver/",
 		"name": "location",
 		"configFile": "remap.config",
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver/",
+		"value": "/usr/etc/trafficserver/",
 		"name": "location",
 		"configFile": "storage.config",
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver/",
+		"value": "/usr/etc/trafficserver/",
 		"name": "location",
 		"configFile": "volume.config",
 		"secure": 0
@@ -1134,7 +1134,7 @@
 		"secure": 0
 	},
 	{
-		"value": "/opt/trafficserver/etc/trafficserver",
+		"value": "/usr/etc/trafficserver",
 		"name": "location",
 		"configFile": "logs_xml.config",
 		"secure": 0
@@ -1236,7 +1236,7 @@
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver",
+		"value": "/usr/etc/trafficserver",
 		"name": "location",
 		"configFile": "regex_revalidate.config",
 		"secure": 0
@@ -1362,7 +1362,7 @@
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver",
+		"value": "/usr/etc/trafficserver",
 		"name": "location",
 		"configFile": "astats.config",
 		"secure": 0
@@ -1470,7 +1470,7 @@
 		"secure": 0
 	},
 	{
-		"value": "STRING /etc/trafficserver/body_factory",
+		"value": "STRING /usr/etc/trafficserver/body_factory",
 		"name": "CONFIG proxy.config.body_factory.template_sets_dir",
 		"configFile": "records.config",
 		"secure": 0
@@ -1566,13 +1566,13 @@
 		"secure": 0
 	},
 	{
-		"value": "/etc/trafficserver",
+		"value": "/usr/etc/trafficserver",
 		"name": "location",
 		"configFile": "ip_allow.config",
 		"secure": 0
 	},
 	{
-		"value": "/var/trafficserver",
+		"value": "/var/trafficserver/",
 		"name": "Drive_Prefix",
 		"configFile": "storage.config",
 		"secure": 0
diff --git a/infrastructure/cdn-in-a-box/edge/run.sh b/infrastructure/cdn-in-a-box/edge/run.sh
new file mode 100755
index 000000000..cc1a2dbb1
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/edge/run.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+
+# 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.
+
+set -e
+set -x
+set -m
+
+source /to-access.sh
+
+# Wait on SSL certificate generation
+until [ -f "$X509_CA_DONE_FILE" ]
+do
+  echo "Waiting on Shared SSL certificate generation"
+  sleep 3
+done
+
+# Source the CIAB-CA shared SSL environment
+source $X509_CA_ENV_FILE
+
+# Trust the CIAB-CA at the System level
+cp $X509_CA_CERT_FILE /etc/pki/ca-trust/source/anchors
+update-ca-trust extract
+
+while ! to-ping 2>/dev/null; do
+	echo "waiting for Traffic Ops"
+	sleep 5
+done
+
+CDN=CDN-in-a-Box
+
+export TO_USER=$TO_ADMIN_USER
+export TO_PASSWORD=$TO_ADMIN_PASSWORD
+
+# wait until the CDN has been registered
+found=
+while [[ -z $found ]]; do
+    echo 'waiting for enroller setup'
+    sleep 3
+    found=$(to-get api/1.3/cdns?name="$CDN" | jq -r '.response[].name')
+done
+
+to-enroll edge $CDN || (while true; do echo "enroll failed."; sleep 3 ; done)
+
+function testenrolled() {
+	local tmp="$(to-get	'api/1.3/servers?name=edge')"
+	tmp=$(echo $tmp | jq '.response[]|select(.hostName=="edge")')
+	echo "$tmp"
+}
+
+while [[ -z "$(testenrolled)" ]]; do
+	echo "waiting on enrollment"
+	sleep 3
+done
+
+# Leaves the container hanging open in the event of a failure for debugging purposes
+/opt/ort/traffic_ops_ort.py BADASS ALL "https://$TO_FQDN:$TO_PORT" "$TO_ADMIN_USER:$TO_ADMIN_PASSWORD" || { echo "Failed"; }
+
+envsubst < "/etc/cron.d/traffic_ops_ort-cron-template" > "/var/spool/cron/root" && rm -f "/etc/cron.d/traffic_ops_ort-cron-template"
+crontab "/var/spool/cron/root"
+
+crond -im off
+
+touch /var/log/trafficserver/diags.log
+tail -F /var/log/trafficserver/diags.log
diff --git a/infrastructure/cdn-in-a-box/edge/server.json b/infrastructure/cdn-in-a-box/edge/server.json
deleted file mode 100644
index 29ab0b68a..000000000
--- a/infrastructure/cdn-in-a-box/edge/server.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-	"hostName": "MY_HOSTNAME",
-	"domainName": "MY_DOMAINNAME",
-	"cachegroupId": CACHE_GROUP_ID,
-	"interfaceName": "MY_IFACE_NAME",
-	"ipAddress": "MY_IP",
-	"ipNetmask": "MY_NETMASK",
-	"ipGateway": "MY_GATEWAY",
-	"interfaceMtu": MY_MTU,
-	"physLocationId": MY_LOCATION,
-	"typeId": MY_TYPE,
-	"profileId": MY_PROFILE_ID,
-	"cdnId": MY_CDN_ID,
-	"updPending": false,
-	"statusId": REPORTED_ID,
-	"tcpPort": 80
-}
diff --git a/infrastructure/cdn-in-a-box/edge/setup.sh b/infrastructure/cdn-in-a-box/edge/setup.sh
deleted file mode 100755
index 677ca5336..000000000
--- a/infrastructure/cdn-in-a-box/edge/setup.sh
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/bash
-
-# 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.
-
-set -e
-set -x
-set -m
-
-traffic_server start &
-
-INFO=$(ifconfig eth0 | grep 'inet ' | tr -s ' ')
-myIP=$(echo $INFO | cut -d' ' -f2)
-device=eth0
-gateway=$(route -n | grep $device | grep -E '^0\.0\.0\.0' | tr -s ' ' | cut -d ' ' -f2)
-mtu=$(ip addr show | grep $device | head -n 1 | cut -d ' ' -f5)
-netmask=$(echo $INFO | cut -d ' ' -f4)
-
-sed -ie "s;MY_HOSTNAME;$(hostname -s);g" /server.json
-sed -ie "s;MY_DOMAINNAME;$(dnsdomainname);g" /server.json
-sed -ie "s;MY_IFACE_NAME;$device;g" /server.json
-sed -ie "s;MY_MTU;$mtu;g" /server.json
-sed -ie "s;MY_GATEWAY;$gateway;g" /server.json
-sed -ie "s;MY_NETMASK;$netmask;g" /server.json
-sed -ie "s;MY_IP;$myIP;g" /server.json
-
-source /to-access.sh
-
-while ! to-ping 2>/dev/null; do
-	echo "waiting for Traffic Ops"
-	sleep 3
-done
-
-
-# Gets our CDN ID
-CDN=$(to-get api/1.3/cdns | jq '.response|.[]|select(.name=="CDN-in-a-Box")|.id')
-while [[ -z "$CDN" ]]; do
-	echo "waiting for trafficops setup to complete..."
-	sleep 3
-	CDN=$(to-get api/1.3/cdns | jq '.response|.[]|select(.name=="CDN-in-a-Box")|.id')
-done
-
-
-# Now we upload a profile for later use
-sed -ie "s;CDN_ID;$CDN;g" /profile.json
-PROFILE=$(to-post api/1.3/profiles /profile.json | jq '.response')
-PROFILENAME=$(echo $PROFILE | jq '.name' | tr -d '"')
-PROFILEID=$(echo $PROFILE | jq '.id')
-to-post api/1.3/profiles/name/$PROFILENAME/parameters /parameters.json
-echo
-
-# Gets the location ID
-location=$(to-get api/1.3/phys_locations | jq '.response|.[]|select(.name=="CDN_in_a_Box")|.id')
-while [[ -z "$location" ]]; do
-	echo "Waiting for location setup"
-	sleep 3
-	location=$(to-get api/1.3/phys_locations | jq '.response|.[]|select(.name=="CDN_in_a_Box")|.id')
-done
-
-# Gets the id of a MID server type
-TYPE=$(to-get api/1.3/types | jq '.response|.[]|select(.name=="EDGE")|.id')
-
-# Gets the id of the 'REPORTED' status
-REPORTED=$(to-get api/1.3/statuses | jq '.response|.[]|select(.name=="REPORTED")|.id')
-
-# Gets the cachegroup ID
-CACHEGROUP=$(to-get api/1.3/cachegroups | jq '.response|.[]|select(.name=="CDN_in_a_Box_Edge")|.id')
-while [[ -z "$CACHEGROUP" ]]; do
-	echo "waiting for trafficops setup to complete..."
-	sleep 3
-	CACHEGROUP=$(to-get api/1.3/cachegroups | jq '.response|.[]|select(.name=="CDN_in_a_Box_Edge")|.id')
-done
-
-# Now put it all together and send it up
-sed -ie "s;MY_LOCATION;$location;g" /server.json
-sed -ie "s;MY_TYPE;$TYPE;g" /server.json
-sed -ie "s;MY_CDN_ID;$CDN;g" /server.json
-sed -ie "s;REPORTED_ID;$REPORTED;g" /server.json
-sed -ie "s;CACHE_GROUP_ID;$CACHEGROUP;g" /server.json
-sed -ie "s;MY_PROFILE_ID;$PROFILEID;g" /server.json
-cat /server.json
-SERVER=$(to-post api/1.3/servers /server.json | jq '.response.id')
-
-
-#finally, link this server to a delivery service
-DSID=$(to-get api/1.3/deliveryservices | jq '.response|.[]|select(.displayName=="CDN in a Box")|.id')
-while [[ -z "$DSID" ]]; do
-	echo "Waiting for delivery service creation..."
-	sleep 3
-	DSID=$(to-get api/1.3/deliveryservices | jq '.response|.[]|select(.displayName=="CDN in a Box")|.id')
-done
-
-to-post api/1.2/deliveryserviceserver "{\"dsId\":$DSID,\"servers\":[$SERVER],\"replace\":true}"
-echo
-
-fg
diff --git a/infrastructure/cdn-in-a-box/edge/traffic_ops_ort.py b/infrastructure/cdn-in-a-box/edge/traffic_ops_ort.py
new file mode 100755
index 000000000..22c4df4ed
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/edge/traffic_ops_ort.py
@@ -0,0 +1,1325 @@
+#!/usr/bin/env python3
+# 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.
+
+
+"""
+This script aims to be a drop-in replacement for the aged
+`traffic_ops_ort.pl` script. Its primary purpose is for
+management of a cache server via configuration from
+Traffic Ops. This script will install/upgrade packages
+as necessary, will ensure services that ought to be running
+are running, and sets up ATS and ATS Plugin configuration files.
+"""
+
+
+import argparse
+import datetime
+import sys
+import os
+import platform
+import typing
+import logging
+import enum
+import subprocess
+import time
+
+needInstall = []
+
+try:
+	import requests
+except ImportError:
+	logging.error("You must have the 'requests' package installed to use this script.")
+	logging.warning("(Hint: try `pip3 install requests`)")
+	needInstall.append("requests")
+
+try:
+	import urllib3
+	urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+except ImportError:
+	logging.error("You must have the `urllib3` library installed to use this script.")
+	logging.warning("(Needed by the `requests` package)")
+	logging.warning("(Hint: try `pip3 install urllib3`)")
+	needInstall.append("urllib3")
+
+try:
+	import distro
+	DISTRO = distro.LinuxDistribution().id()
+except ImportError:
+	logging.error("You must have the `distro` package installed to use this script.")
+	logging.warning("Hint: try `pip3 install distro`)")
+	needInstall.append("distro")
+
+try:
+	import psutil
+except ImportError:
+	logging.error("You must have the `psutil` package installed to use this script.")
+	logging.warning("(Hint: try `pip3 install psutil`)")
+	needInstall.append("psutil")
+
+__version__ = "0.1"
+
+# Holds the info needed to query Traffic Ops
+TO_URL, TO_LOGIN, TO_COOKIE, TS_ROOT = (None,)*4
+
+# Logging info
+LOG_LEVELS = {"ALL":   logging.NOTSET,
+              "DEBUG": logging.DEBUG,
+              "INFO":  logging.INFO,
+              "WARN":  logging.WARNING,
+              "ERROR": logging.ERROR,
+              "FATAL": logging.CRITICAL}
+FMT = "%(levelname)s: line %(lineno)d in %(module)s.%(funcName)s: %(message)s"
+
+# Not strictly accurate, but generally good enough
+HOSTNAME = (platform.node().split('.')[0], platform.node())
+
+class Modes(enum.IntEnum):
+	"""
+	Enumerated run modes
+	"""
+	REPORT = 0
+	INTERACTIVE = 1
+	REVALIDATE = 2
+	SYNCDS = 3
+	BADASS = 4
+
+	def __str__(self) -> str:
+		"""
+		Implements `str(self)` by returning enum member's name
+		"""
+		return self.name
+
+class ORTException(Exception):
+	"""Represents an error while processing ORT API responses, etc."""
+	pass
+
+
+# Current Run Mode
+MODE = None
+
+
+#This is the set of files which will require an ATS restart upon changes
+ATS_FILES = {"records.config",
+             "remap.config",
+             "parent.config",
+             "cache.config",
+             "hosting.config",
+             "astats.config",
+             "logs_xml.config",
+             "ssl_multicert.config"}
+ATS_NEEDS_RESTART = False
+
+###############################################################################
+#####                                                                     #####
+#####                     PYTHON DEPENDENCY HANDLING                      #####
+#####                                                                     #####
+###############################################################################
+def installPythonPackages(packages:typing.List[str]) -> bool:
+	"""
+	Attempts to install the packages listed in `packages` and
+	add them to the global scope.
+
+	Returns a truthy value indicating success.
+	"""
+	logging.info("Attempting install of %s", ','.join(packages))
+
+	# Ensure `pip` is installed
+	try:
+		import pip
+	except ImportError as e:
+		logging.info("`pip` package not installed. Attempting install with `ensurepip` module")
+		logging.debug("%s", e, exc_info=True, stack_info=True)
+		import ensurepip
+		try:
+			ensurepip.bootstrap(altinstall=True)
+		except (EnvironmentError, PermissionError) as err:
+			logging.info("Permission Denied, attempting user-level install")
+			logging.debug("%s", err, exc_info=True, stack_info=True)
+			ensurepip.bootstrap(altinstall=True, user=True)
+		import pip
+
+	# Get main pip function
+	try:
+		pipmain = pip.main
+	except AttributeError as e: # This happens when using a version of pip >= 10.0
+		from pip._internal import main as pipmain
+
+
+	# Attempt package install
+	ret = pipmain(["install"] + packages)
+	if ret == 1:
+		logging.info("Possible 'Permission Denied' error, attempting user-level install")
+		ret = pipmain(["install", "--user"] + packages)
+
+	logging.debug("Pip return code was %d", ret)
+	if not ret:
+		import importlib
+		for package in packages:
+			try:
+				globals()[package] = importlib.import_module(package)
+			except ImportError:
+				logging.error("Failed to import %s", package)
+				logging.warning("Install appeared succesful - subsequent run may succeed")
+				logging.debug("%s", e, exc_info=True, stack_info=True)
+				return False
+
+		urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+		globals()["DISTRO"] = distro.LinuxDistribution().id()
+
+	return not ret
+
+def handleMissingPythonPackages(packages:typing.List[str]) -> bool:
+	"""
+	Handles the case of missing packages.
+
+	Installs packages by calling `installPythonPackages`.
+	"""
+	logging.info("Packages needed for this script but not installed: %s", ','.join(packages))
+
+	if MODE == Modes.BADASS:
+		logging.warning("Mode is BADASS - attempting to install missing packages")
+		doInstall = True
+	elif MODE == Modes.INTERACTIVE:
+		print("The following packages are needed to run this script, but are not installed:")
+		print(','.join(packages))
+		choice = input("Would you like the script to attempt to install them now? [y/N]: ")
+
+		while choice and choice.lower() not in {'y', 'n', 'yes', 'no'}:
+			print("Invalid choice:", choice, file=sys.stderr)
+			choice = input("Would you like the script to attempt to install them now? [y/N]: ")
+
+		doInstall = choice and choice.lower() in {'y', 'yes'}
+	else:
+		logging.error("Cannot install packages in current run mode.")
+		return False
+
+	if doInstall:
+		logging.warning("Installing python packages: %s", ','.join(packages))
+		if installPythonPackages(packages):
+			logging.info("Python packages installed successfully.")
+		else:
+			logging.critical("Missing Python packages could not be installed.")
+			return False
+	else:
+		logging.critical("Packages %s missing and will not be installed - cannot continue",
+		                 ','.join(packages))
+		return False
+
+	return True
+
+
+###############################################################################
+#####                                                                     #####
+#####                              UTILITIES                              #####
+#####                                                                     #####
+###############################################################################
+def getJSONResponse(uri:str, expectedStatus:int = 200) -> object:
+	"""
+	Returns the JSON-encoded contents (as a `dict`) of the response for a GET request to `uri`
+	"""
+	global TO_URL, TO_COOKIE
+
+	logging.info("Getting json response via 'HTTP GET %s%s", TO_URL, uri)
+
+	response = requests.get(TO_URL + uri, cookies=TO_COOKIE, verify=False)
+
+	if response.status_code != expectedStatus:
+		logging.error("Failed to get a response from '%s': server returned status code %d",
+		              uri,
+		              response.status_code)
+		logging.debug("Response: %s\n%r\n%r", response, response.headers, response.content)
+		# For some applications, empty responses may be valid, so returning None rather than exiting.
+		return None
+
+	return response.json()
+
+def getRawResponse(uri:str, expectedStatus:int=200, TOrelative:bool=True, verify:bool=False) ->str:
+	"""
+	Returns the raw body of a GET request for the specified URI.
+	(actually encodes to utf-8 string)
+
+	Note that the behaviour of treating the uri as relative to TO_URL may be overridden, unlike
+	`getJSONResponse`.
+	"""
+	global TO_URL, TO_COOKIE
+
+	if TOrelative:
+		uri = TO_URL + uri
+
+	response = requests.get(uri, cookies=TO_COOKIE, verify=verify)
+	if response.status_code != expectedStatus:
+		logging.error("Failed to get a response from '%s': server returned status code %d",
+		              uri,
+		              response.status_code)
+		logging.debug("Response: %s\n%r\n%r", response, response.headers, response.content)
+		return None
+
+	return response.text
+
+def setStatusFile(statusDir:str, status:str, create:bool = False):
+	"""
+	Removes all files in `statusDir` that aren't `status`, and creates `status`
+	if it doesn't exist and `create` is True.
+
+	Raises an OSError if that fails.
+	"""
+	global MODE
+
+	logging.info("Setting status file")
+
+	if not os.path.isdir(statusDir):
+		logging.info("Creating directory %s", statusDir)
+		if MODE:
+			os.mkdir(statusDir)
+	else:
+		try:
+			statuses = getJSONResponse("/api/1.3/statuses")['response']
+		except (KeyError, AttributeError) as e:
+			logging.error("Bad API response from /api/1.3/statuses")
+			logging.warning("Terminating status file creation/cleanup prematurely.")
+			logging.debug("%s", e, exc_info=True, stack_info=True)
+			return
+
+		for stat in statuses:
+			fname = os.path.join(statusDir, stat["name"])
+			if stat != status and os.path.isfile(fname):
+				logging.info("Removing %s", fname)
+				if not MODE == Modes.REPORT:
+					os.remove(fname)
+
+	fname = os.path.join(statusDir, status)
+	if create and not os.path.isfile(fname):
+		logging.info("creating %s", fname)
+		if MODE:
+			with open(os.path.join(statusDir, status), "x"):
+				pass
+
+#pylint: disable=R1710
+def startDaemon(args:typing.List[str], stdout:str='/dev/null', stderr:str='/dev/null') -> bool:
+	"""
+	Starts a daemon process to execute the command line given by 'args'
+	and returns a boolean indicating success.
+
+	Note that this can only indicate the success of the first fork.
+
+	The first fork will exit successfully as long as the second fork doesn't
+	raise an OSError. The second fork will exit with the same returncode as
+	the exec'd process.
+	"""
+
+	logging.debug("Forking a process - argv: '%s'", ' '.join(args))
+
+	try:
+		pid = os.fork()
+	except OSError as e:
+		logging.error("First fork failed - aborting")
+		logging.debug("%s", e, exc_info=True, stack_info=True)
+		return False
+
+	if pid:
+		# This is the parent
+		return True
+
+	# De-couple from parent environment
+	os.chdir('/')
+	try:
+		os.setsid()
+	except PermissionError:
+		logging.debug("Failure to `setsid`: pid=%d, pgid=%d", os.getpid(), os.getpgid(os.getpid()))
+		logging.debug("", exc_info=True, stack_info=True)
+	os.umask(0)
+	sys.stdin = open('/dev/null')
+	sys.stdout = open(stdout, 'w')
+	sys.stdout = open(stderr, 'w')
+
+	# Do the second fork magic
+
+	try:
+		pid = os.fork()
+	except OSError as e:
+		logging.error("Error in forked process: %s", e)
+		logging.debug("", exc_info=True, stack_info=True)
+		exit(1)
+
+	if pid:
+		# This is the parent
+		exit(0)
+
+	# Now actually exec the program
+	try:
+		os.execvp(args[0], args[1:])
+	except OSError:
+		logging.critical("Failure to start %s", args[0])
+		logging.debug("", exc_info=True, stack_info=True)
+
+	# If somehow we get down here, it's time to bail
+	exit(1)
+#pylint: enable=R1710
+
+def setATSStatus(status:bool, restart:bool = False) -> bool:
+	"""
+	Sets the status of the system's ATS process to on if `status` is True, else off.
+	If `restart` is True, then ATS will be restarted if already running.
+	(`restart` has no effect if `status` is False)
+
+	Returns a boolean indicator of success.
+	"""
+	global MODE, TS_ROOT
+
+	logging.debug("Iterating process list")
+
+	arg = None
+	for process in psutil.process_iter():
+
+		# Found an ATS process
+		if process.name() == "[TS_MAIN]":
+			logging.debug("ATS process found (pid: %d)", process.pid)
+			ATSAlreadyRunning = process.status() in {psutil.STATUS_RUNNING, psutil.STATUS_SLEEPING}
+
+			if status and ATSAlreadyRunning and restart:
+				logging.info("ATS process found; restarting")
+				arg = "restart"
+			elif status and ATSAlreadyRunning:
+				logging.info("ATS already running; nothing to do.")
+			elif status:
+				logging.warning("ATS process is running, but status is '%s' - restarting", process.status())
+				arg = "restart"
+			else:
+				logging.warning("ATS is running; stopping ATS")
+				arg = "stop"
+
+			break
+	else:
+		if status:
+			logging.warning("ATS not already running; starting ATS.")
+			arg = "start"
+		else:
+			logging.info("ATS already not running; nothing to do.")
+
+	if arg and MODE != "REPORT":
+		tsexe = os.path.join(TS_ROOT, "bin", "trafficserver")
+		sub = subprocess.Popen([tsexe, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+		out, err = sub.communicate()
+
+		if sub.returncode:
+			logging.error("Failed to start trafficserver!")
+			logging.warning("Is the 'trafficserver' script located at %s?", tsexe)
+			logging.debug(out.decode())
+			logging.debug(err.decode())
+			return False
+
+	return True
+
+def getHeaderComment() -> str:
+	"""
+	Gets the header for the Traffic Ops system
+	"""
+	response = getJSONResponse("/api/1.3/system/info.json")
+	logging.debug("system/info.json response: %s", response)
+
+	if response is None or \
+	   "response" not in response or \
+	   "parameters" not in response["response"] or \
+	   "tm.toolname" not in response["response"]["parameters"]:
+		logging.error("Did not find tm.toolname!")
+		return ''
+
+	tmToolname = response["response"]["parameters"]["tm.toolname"]
+	logging.info("Found tm.toolname: %s", tmToolname)
+
+	return tmToolname
+
+def setTO_LOGIN(login:str) -> str:
+	"""
+	Parses the passed login and returns a
+	JSON string used for login.
+
+	Will test the login before returning, and
+	raise a `PermissionError` if credentials
+	are refused. Also sets the TO_COOKIE
+	global variable.
+	"""
+	global TO_COOKIE
+
+	login = '{{"u": "{0}", "p": "{1}"}}'.format(*login.split(':'))
+
+	logging.debug("TO_LOGIN: %s", login)
+
+	# Obtain login cookie
+	cookie = requests.post(TO_URL + '/api/1.3/user/login', data=login, verify=False)
+
+	if not cookie.cookies or 'mojolicious' not in cookie.cookies:
+		logging.error("Response code: %d", cookie.status_code)
+		logging.warning("Response Headers: %s", cookie.headers)
+		logging.debug("Response: %s", cookie.content)
+		raise PermissionError("Login credentials rejected by Traffic Ops")
+
+	TO_COOKIE = {"mojolicious": cookie.cookies["mojolicious"]}
+
+	return login
+
+def getYesNoResponse(prmpt:str, default:str = None) -> bool:
+	"""
+	Utility function to get an interactive yes/no response to the prompt `prmpt`
+	"""
+	if default:
+		prmpt = prmpt.rstrip().rstrip(':') + '['+default+"]:"
+	while True:
+		choice = input(prmpt).lower()
+
+		if choice in {'y', 'yes'}:
+			return True
+		elif choice in {'n', 'no'}:
+			return False
+		elif not choice and default is not None:
+			return default.lower() in {'y', 'yes'}
+
+		print("Please enter a yes/no response.", file=sys.stderr)
+
+###############################################################################
+#####                                                                     #####
+#####                         MAIN MODE ROUTINES                          #####
+#####                                                                     #####
+###############################################################################
+def syncDSState() -> bool:
+	"""
+	Queries Traffic Ops for the Delivery Service's sync state.
+
+	Returns True if an update is needed, False if it isn't.
+	If something goes wrong, it'll raise an ORTException containing the error
+	message.
+	"""
+	global HOSTNAME
+
+	logging.info("starting syncDS State fetch")
+
+	try:
+		updateStatus = getJSONResponse("/api/1.3/servers/%s/update_status" % HOSTNAME[0])[0]
+	except IndexError as e:
+		logging.critical("Server not found in Traffic Ops config")
+		logging.debug("%s", e, exc_info=True, stack_info=True)
+		raise ORTException("Failed to contact API endpoint.")
+
+	try:
+		if not updateStatus['upd_pending']:
+			logging.info("No update pending.")
+			return False
+
+		statusDir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "status")
+		setStatusFile(statusDir, updateStatus['status'], create=True)
+	except (KeyError, AttributeError) as e:
+		logging.critical("Unsupported Traffic Ops version.")
+		logging.warning("%s", e, exc_info=True)
+		logging.debug("", stack_info=True)
+		raise ORTException("Traffic Ops version not supported")
+
+	return True
+
+def revalidate() -> int:
+	"""
+	Performs revalidation.
+	"""
+	global HOSTNAME
+
+	logging.info("starting revalidation")
+
+	try:
+		updateStatus = getJSONResponse("/api/1.3/servers/%s/update_status" % HOSTNAME[0])[0]
+	except IndexError as e:
+		logging.critical("Server not found in Traffic Ops config")
+		logging.debug("%s", e, exc_info=True, stack_info=True)
+		return 2
+
+	logging.debug("updateStatus raw response: %s", updateStatus)
+
+	try:
+		if not updateStatus['reval_pending']:
+			logging.info("No revalidation pending.")
+			return 1
+
+		if updateStatus['parent_reval_pending']:
+			logging.critical("Parent revalidation is pending.")
+			return 1
+
+		statusDir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "status")
+		setStatusFile(statusDir, updateStatus['status'])
+	except (KeyError, AttributeError):
+		logging.critical("Unsupported Traffic Ops version")
+		logging.warning("%s", e, exc_info=True)
+		logging.debug("%s", e, stack_info=True)
+		return 2
+
+	return 0
+
+def updateOps() -> int:
+	"""
+	Updates Traffic Ops as needed, and returns an exit code for the main routine
+	"""
+	global MODE, HOSTNAME, TO_URL, TO_COOKIE
+
+	revalPending = revalidate() == 0
+
+	if (MODE==Modes.INTERACTIVE and getYesNoResponse("Update Traffic Ops?", 'Y'))\
+	   or MODE!=Modes.REVALIDATE:
+
+		logging.info("starting Traffic Ops update for upd_pending")
+		payload = {"updated": False, "reval_updated": False}
+		response = requests.post(TO_URL+"/update/%s" % HOSTNAME[0], cookies=TO_COOKIE, verify=False, data=payload)
+
+		logging.debug("Raw response from Traffic Ops: %s\n%s\n%s", response, response.headers, response.content)
+
+	elif MODE == Modes.REVALIDATE:
+		logging.info("starting Traffic Ops update for reval_pending")
+		payload = {"updated": False, "reval_updated": True}
+		response = requests.post(TO_URL+"/update/%s" % HOSTNAME[0], cookies=TO_COOKIE, verify=False, data=payload)
+
+		logging.debug("Raw response from Traffic Ops: %s\n%s\n%s", response, response.headers, response.content)
+
+	else:
+		logging.warning("Update will not be performed at this time; you should do this manually")
+	return 0
+
+
+HANDLERS = {"revalidate": revalidate,
+            "syncds": lambda: None}
+
+
+###############################################################################
+#####                                                                     #####
+#####                         PACKAGE MANAGEMENT                          #####
+#####                                                                     #####
+###############################################################################
+
+########################
+###      RedHat      ###
+########################
+def RedHatInstalled(package:str, version:str = None) -> typing.List[str]:
+	"""
+	Returns the list of packages installed by the name 'package',
+	optionally with a specific version.
+	"""
+	logging.debug("Checking for RedHat-like package %s", package)
+	arg = package if not version else package + '-' + version
+
+	# This should never be done, but CentOS insists on being behind the rest of the
+	# world, so done it must be.
+	sub = subprocess.Popen(["/bin/rpm", "-q", arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+	out, err = sub.communicate()
+
+	if sub.returncode:
+		# logging.error("Failed to query rpm database for %s", package)
+		logging.debug(out.decode())
+		logging.debug(err.decode())
+		return []
+
+	return out.decode().split()
+
+def RedHatInstall(packages:typing.List[str]) -> bool:
+	"""
+	Installs the packages in the `packages` list and
+	returns a boolean success indicator.
+	"""
+	sub = subprocess.Popen(["/bin/yum", "install", "-y"] + packages,
+	                       stdout=subprocess.PIPE,
+	                       stderr=subprocess.PIPE)
+	out, err = sub.communicate()
+
+	if sub.returncode:
+		logging.debug("yum stdout: %s", out.decode())
+		logging.debug("yum stderr: %s", err.decode())
+		return False
+
+	logging.info("Successfully installed packages: %s", ", ".join(packages))
+	return True
+
+def RedHatUninstall(packages:typing.List[str]) -> bool:
+	"""
+	Removes the packages in the `packages` list, and
+	returns a boolean indicator of success.
+	"""
+	sub = subprocess.Popen(["/bin/yum", "remove", "-y"] + packages,
+	                       stdout=subprocess.PIPE,
+	                       stderr=subprocess.PIPE)
+	out, err = sub.communicate()
+
+	if sub.returncode:
+		logging.debug("yum stdout: %s", out.decode())
+		logging.debug("yum stderr: %s", err.decode())
+		return False
+
+	logging.info("Successfully uninstalled packages: %s", ", ".join(packages))
+	return True
+
+########################
+###      Ubuntu      ###
+########################
+def UbuntuInstalled(package:str, version:str = None) -> typing.List[str]:
+	"""
+	Returns the list of packages installed by the name 'package',
+	optionally with a specific version.
+	"""
+	logging.debug("Checking for Ubuntu-like package %s", package)
+
+	sub = subprocess.Popen(["/usr/bin/dpkg", "-l", package], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+	out, err = sub.communicate()
+
+	if sub.returncode:
+		logging.debug("dpkg stdout: %s", out.decode())
+		logging.debug("dpkg stderr: %s", err.decode())
+		return []
+
+	try:
+		out = [(p.split()[1], p.split()[2]) for p in out[5:].decode().splitlines() if p]
+
+		if version is not None:
+			# TODO - better version checking
+			out = [p for p in out if version in p[1]]
+
+		return [p[0] for p in out]
+	except IndexError:
+		logging.warning("Error encountered while processing installed Debian packages")
+		logging.debug("Package: %s", package, exc_info=True, stack_info=True)
+
+	return []
+
+def UbuntuInstall(packages:typing.List[str]) -> bool:
+	"""
+	Installs the packages in the `packages` list and
+	returns a boolean success indicator
+	"""
+	sub = subprocess.Popen(["/usr/bin/apt-get", "install", "-y"] + packages,
+	                       stdout=subprocess.PIPE,
+	                       stderr=subprocess.PIPE)
+	out, err = sub.communicate()
+
+	if sub.returncode:
+		logging.debug("apt-get stdout: %s", out.decode())
+		logging.debug("apt-get stderr: %s", err.decode())
+		return False
+
+	logging.info("Successfully installed packages: %s", ", ".join(packages))
+	return True
+
+def UbuntuUninstall(packages:typing.List[str]) -> bool:
+	"""
+	Uninstalls the packages in the `packages` list and returns a
+	boolean indicator of success.
+	"""
+	sub = subprocess.Popen(["/usr/bin/apt-get", "purge", "-y"] + packages,
+	                       stdout=subprocess.PIPE,
+	                       stderr=subprocess.PIPE)
+	out, err = subprocess.communicate()
+
+	if sub.returncode:
+		logging.debug("apt-get stdout: %s", out.decode())
+		logging.debug("apt-get stderr: %s", err.decode())
+		return False
+
+	logging.info("Successfully uninstalled packages: %s", ", ".join(packages))
+	return True
+
+########################
+### Platform Mapping ###
+########################
+packageIsInstalled = {'centos': RedHatInstalled,
+                      'fedora': RedHatInstalled,
+                      'rhel': RedHatInstalled,
+                      'ubuntu': UbuntuInstalled,
+                      'linuxmint': UbuntuInstalled}
+installPackage = {'centos': RedHatInstall,
+                  'fedora': RedHatInstall,
+                  'rhel': RedHatInstall,
+                  'ubuntu': UbuntuInstall,
+                  'linuxmint': UbuntuInstall}
+uninstallPackage = {'centos': RedHatUninstall,
+                    'fedora': RedHatUninstall,
+                    'rhel': RedHatUninstall,
+                    'ubuntu': UbuntuUninstall,
+                    'linuxmint': UbuntuUninstall}
+packageConcat = {'centos': '-',
+                 'fedora': '-',
+                 'rhel': '-',
+                 'ubuntu': '=',
+                 'linuxmint': '='}
+
+def processPackages() -> bool:
+	"""
+	Manages the packages that Traffic Ops reports are required for this server.
+
+	Returns a boolean indication of success.
+	"""
+	global HOSTNAME, DISTRO, MODE, packageIsInstalled, installPackage, packageConcat
+
+	logging.info("Fetching packages from Traffic Ops")
+	packages = getJSONResponse("/ort/%s/packages" % (HOSTNAME[0],))
+	logging.info("Response: %s", packages)
+
+	if packages is None:
+		raise ConnectionError("Server or server packages not found on server!")
+
+	install, uninstall = {p['name'] + packageConcat[DISTRO] + p['version'] for p in packages}, set()
+	for package in packages:
+		similarPackages = packageIsInstalled[DISTRO](package['name'])
+
+		# An error occurred, and the package query failed (different than empty response)
+		if similarPackages is None:
+			logging.critical("Failed to check packages against system.")
+			return False
+
+		logging.debug("List of packages similar to %r: %r", package, similarPackages)
+
+		# We check for the pre-existence of packages in two ways; one for
+		# RedHat-based distros, and one for Debian/Ubuntu-based distros
+
+		if any(p.startswith(
+		          package['name']+packageConcat[DISTRO]+package['version'])
+		        for p in similarPackages):
+			logging.info("package %s is installed", package['name'])
+			p = package['name'] + packageConcat[DISTRO] + package['version']
+			logging.info("%s is no longer marked for install", p)
+			install.remove(p)
+		else:
+			logging.info("%r all marked for uninstall", similarPackages)
+			uninstall.update(similarPackages)
+
+	logging.info("Marked %d packages for install and %d packages for uninstall.",
+	               len(install),               len(uninstall))
+
+	if not MODE == Modes.REPORT:
+
+		logging.info("Installing packages...")
+		if install:
+			if MODE == INTERACTIVE and not getYesNoResponse("Would you like to install the following packages: %s ?"%", ".join(install)):
+				logging.critical("User chose not to install packages - cannot continue!")
+				return False
+
+			if not installPackage[DISTRO](list(install)):
+				logging.critical("Failed to install packages, possibly permission denied?")
+				return False
+
+		logging.info("Done.")
+
+
+		logging.info("Uninstalling packages...")
+		if uninstall:
+			if MODE == INTERACTIVE and not getYesNoResponse("Would you like to remove the following packages: %s ?"%", ".join(uninstall)):
+				logging.critical("User chose not to remove packages - cannot continue!")
+				return False
+
+			if not uninstallPackage[DISTRO](list(uninstall)):
+				logging.critical("Failed to uninstall packages, possibly permission denied?")
+				return False
+
+		logging.info("Done.")
+
+	return True
+
+def processChkconfig() -> bool:
+	"""
+	'Processes chkconfig' - whatever that means/is
+
+	Returns a boolean indicator of success.
+	"""
+	global HOSTNAME, MODE, DISTRO
+
+	logging.info("Processesing Chkconfig...")
+	chkconfig = getJSONResponse("/ort/%s/chkconfig" % HOSTNAME[0])
+
+	if chkconfig is None:
+		raise ConnectionError("Server or server chkconfig not found on server!")
+
+	logging.debug("chkconfig response: %r", chkconfig)
+
+	for item in chkconfig:
+		logging.debug("Processing item: %r", item)
+
+		# A special catch for ATS so we don't need to deal with systemd.
+		if item['name'] == "trafficserver":
+			if not setATSStatus("on" in item['value']):
+				logging.critical("Failed to set ATS Status")
+				return False
+		else:
+			logging.info("checking on service: %s", item['name'])
+
+
+	return True
+
+
+###############################################################################
+#####                                                                     #####
+#####                           CONFIGURATION                             #####
+#####                                                                     #####
+###############################################################################
+def getConfigFiles() -> typing.List[str]:
+	"""
+	Gets the list of configuration files used by this server's profile
+	"""
+	global HOSTNAME
+
+	logging.info("Retrieving configuration files.")
+
+	files = getJSONResponse("/api/1.3/servers/%s/configfiles/ats" % (HOSTNAME[0],))
+
+	if not files:
+		logging.critical("Could not retrieve configuration files.")
+		return None
+
+	logging.debug("Config Files raw response: %s", files)
+
+	return files
+
+def initBackup() -> bool:
+	"""
+	Initializes a backup directory as a subdirectory of the directory containing
+	this ORT script.
+	"""
+	global MODE
+
+	here = os.path.abspath(os.path.dirname(__file__))
+	backupdir = os.path.join(here, "backup")
+
+	logging.info("Initializing backup dir %s", backupdir)
+
+	if not os.path.isdir(backupdir):
+		if MODE != Modes.REPORT:
+			try:
+				os.mkdir(backupdir)
+			except OSError:
+				logging.error("Couldn't create backup dir")
+				logging.warning("%s", e)
+				logging.debug("", exc_info=True, stack_info=True)
+				return False
+		else:
+			logging.error("Cannot create non-existent backup dir in REPORT mode!")
+			return True
+
+	logging.info("Backup dir already exists - nothing to do")
+	return True
+
+def mkbackup(fname:str, contents:str) -> bool:
+	"""
+	Creates a backup file named 'fname' with the contents `contents` and returns
+	True if the operation succeeded, else False.
+
+	Note: will always return True in REPORT mode.
+	"""
+	global MODE
+
+	if MODE == Modes.REPORT:
+		logging.info("REPORT mode - nothing to do")
+		return True
+
+	backupfile = os.path.join(os.path.abspath(os.path.dirname(__file__)), "backup", fname)
+	if os.path.isfile(backupfile):
+		logging.warning("Clobbering existing backup file '%s'!", backupfile)
+
+	try:
+		with open(backupfile, 'w') as fd:
+			fd.write(contents)
+	except OSError as e:
+		logging.warning("Failed to write backup file: %s", e)
+		logging.debug("", exc_info=True, stack_info=True)
+		return False
+
+	logging.info("Backup of %s written to %s", fname, backupfile)
+	return True
+
+def updateConfig(directory:str, fname:str, contents:str) -> bool:
+	"""
+	Updates the config file specified by `file` to contain `contents`.
+
+	Returns a boolean indicator of success.
+	"Success" is defined as being able to write the file contents and create any necessary backups
+	if the mode is not BADASS - in which case failure to back the file up is 'acceptable'.
+
+	This will make a backup in the `backup` subdirectory of the directory
+	containing this script if the file on disk differs from `contents`.
+	"""
+	global MODE, ATS_FILES, ATS_NEEDS_RESTART
+
+	file = os.path.join(directory, fname)
+
+	logging.info("Udpating config file '%s'", file)
+
+	if not os.path.isfile(file):
+		if MODE != Modes.REPORT:
+			logging.info("File does not exist - creating")
+			try:
+				with open(file, 'w') as fd:
+					fd.write(contents)
+			except OSError as e:
+				logging.error("Couldn't write to file")
+				logging.warning("%s", e)
+				logging.debug("", exc_info=True, stack_info=True)
+				return False
+
+			logging.info("File written.")
+
+		return True
+
+	logging.debug("Reading in file on disk")
+	try:
+		with open(file) as fd:
+			diskContents = fd.read()
+	except OSError:
+		logging.warning("Couldn't read on-disk file: %s", e)
+		logging.debug("", exc_info=True, stack_info=True)
+		if MODE != Modes.BADASS:
+			return False
+
+	if diskContents.strip() == contents.strip():
+		logging.info("on-disk contents match Traffic Ops - nothing to do")
+		return True
+
+	if not mkbackup(os.path.basename(file), diskContents) and MODE != Modes.BADASS:
+		logging.error("Failed to create backup.")
+		return False
+
+	try:
+		with open(file, 'w') as fd:
+			fd.write(contents)
+	except OSError as e:
+		logging.error("Failed to update config file '%s'", file)
+		logging.warning("%s", e)
+		logging.debug("", exc_info=True, stack_info=True)
+		return False
+
+	# If update was needed and successful, then check if ats should be restarted
+	if fname in ATS_FILES:
+		ATS_NEEDS_RESTART = True
+
+	logging.info("%s has been updated", file)
+	return True
+
+def sanitizeContents(contents:str) -> str:
+	"""
+	Sanitizes the input `contents` string to be a well-behaved config file.
+	"""
+	out = []
+	for line in contents.splitlines():
+		tmp=(" ".join(line.split())).strip() #squeezes spaces and trims leading and trailing spaces
+		tmp=tmp.replace("&amp;", '&') #decodes HTML-encoded ampersands
+		tmp=tmp.replace("&gt;", '>') #decodes HTML-encoded greater-than symbols
+		tmp=tmp.replace("&lt;", '<') #decodes HTML-encoded less-than symbols
+		out.append(tmp)
+
+	return "\n".join(out)
+
+def processConfigFile(file:dict, port:int, ip:str) -> bool:
+	"""
+	Process the passed file and value to apply a configuration.
+
+	Returns a boolean indicator of success
+	"""
+	global MODE, HOSTNAME, TO_URL
+
+	try:
+		fname = file['fnameOnDisk']
+		scope = file['scope']
+		location = file['location']
+		uri, contents = None, None
+		if 'apiUri' in file:
+			uri = TO_URL + file['apiUri']
+		elif 'url' in file:
+			uri = file['url']
+		else:
+			contents = file['contents']
+	except KeyError as e:
+		logging.error("Malformed config file")
+		logging.warning("%s", e)
+		logging.debug("", exc_info=True, stack_info=True)
+		return False
+
+	logging.info("======== Start processing config file: %s ========", fname)
+
+	if not os.path.isdir(location):
+		if MODE != Modes.REPORT:
+			logging.debug("location '%s' doesn't exist; creating.")
+
+			try:
+				os.makedirs(location)
+			except OSError as e:
+				logging.error("Couldn't create directory %s", location)
+				logging.warning("%s", e)
+				logging.debug("", exc_info=True, stack_info=True)
+				return False
+		else:
+			# Even though nothing was done and an error gets reported, we return a success here
+			# because presumably everything would go fine were this not REPORT mode.
+			logging.error("Cannot create dirs in REPORT mode!")
+			return True
+
+	# `contents` should only be `None` if `uri` is defined
+	if contents is None:
+		contents = getRawResponse(uri, TOrelative=False)
+
+	if contents is None: #still...
+		return False
+
+	contents = contents.replace("__HOSTNAME__", HOSTNAME[0])
+	contents = contents.replace("__FULL_HOSTNAME__", HOSTNAME[1])
+	contents = contents.replace("__RETURN__", '\n')
+	contents = contents.replace("__CACHE_IPV4__", ip)
+
+	# Don't ask me why, but the reference ORT implementation just strips these ones out
+	# if the tcp port is 80.
+	contents = contents.replace("__SERVER_TCP_PORT__", str(port) if port != 80 else "")
+
+	contents = sanitizeContents(contents)
+
+	logging.debug("Sanitized file contents from Traffic Ops database:\n%s\n" % contents)
+
+	logging.warning("Skipping pre-requisite checks - plugins may not be installed!!")
+	logging.info("Dependent packages should be specified as profile parameters.")
+
+	updateConfig(location, fname, contents)
+
+	logging.info("======== End processing config file: %s ========", fname)
+
+	return True
+
+def processConfigFiles(files:list, port:int, ip:str) -> bool:
+	"""
+	processes the passed JSON object containing config file definitions
+
+	Returns a boolean indicator of success
+	"""
+	global MODE
+
+	if not initBackup():
+		return False
+
+	for file in files:
+		if not processConfigFile(file, port, ip):
+			logging.error("Failed to process config file '%s'", file)
+
+			if MODE != Modes.BADASS:
+				return False
+
+			logging.warning("We're in BADASS mode, attempting to continue")
+
+	return True
+
+
+###############################################################################
+#####                                                                     #####
+#####                             MAIN FLOW                               #####
+#####                                                                     #####
+###############################################################################
+def doMain() -> int:
+	"""
+	Performs operations based on the run mode.
+	This can be thought of as the "true" main function.
+	"""
+	global MODE, ATS_NEEDS_RESTART
+
+	headerComment = getHeaderComment()
+
+	try:
+		DSUpdateNeeded = syncDSState()
+	except ORTException as e:
+		logging.critical("Failed to get Update Pending from Traffic Ops: %s", e)
+		return 1
+
+	myFiles = getConfigFiles()
+	if myFiles is None:
+		return 1
+	if "configFiles" not in myFiles or "info" not in myFiles:
+		logging.critical("Malformed response from configfiles/ats endpoint - unable to continue")
+		return 1
+
+	try:
+		# This is needed for templated responses from the config file API endpoints
+		tcpPort = myFiles["info"]["serverTcpPort"]
+		serverIpv4 = myFiles["info"]["serverIpv4"]
+	except KeyError:
+		logging.critical("Malformed response from configfiles/ats endpoint - unable to continue")
+		logging.debug("", exc_info=True, stack_info=True)
+		return 1
+
+
+	try:
+		if MODE == Modes.REVALIDATE:
+			logging.info("======== Revalidating, no package processing needed ========")
+			reval = revalidate()
+			if reval:
+				# Bail, possibly with an exit code
+				return reval - 1
+
+		else:
+			logging.info("======== Start processing packages ========")
+			if not processPackages():
+				logging.critical("Unrecoverable error occurred when processing packages.")
+				return 1
+			logging.info("======== Start processing services ========")
+			if not processChkconfig():
+				logging.critical("Unrecoverable error occurred when processing services.")
+				return 1
+
+		if not processConfigFiles(myFiles["configFiles"], tcpPort, serverIpv4):
+			logging.critical("Unrecoverable error occurred when processing config files")
+			return 1
+	except ConnectionError:
+		logging.critical("Couldn't reach /ort/%s/* endpoints - "\
+		                 "ensure %s points to Traffic OPS not Traffic PORTAL",
+		                 HOSTNAME[0],
+		                 TO_URL)
+		return 1
+
+	if ATS_NEEDS_RESTART:
+		logging.info("Restarting ATS")
+		if not setATSStatus(True, restart=True):
+			logging.critical("Failed to restart ATS")
+			return 1
+	else:
+		logging.info("ATS Restart unnecessary")
+
+	if MODE != Modes.REPORT and DSUpdateNeeded:
+		return updateOps()
+
+	logging.info("Ops update unnecessary; exiting.")
+
+	return 0
+
+def main() -> int:
+	"""
+	Runs the program, returns an exit code
+	"""
+	global TO_COOKIE, TO_LOGIN, TO_URL, LOG_LEVELS, MODE, needInstall, DISTRO, TS_ROOT, FMT
+
+	# I have no idea why, but the old ORT script does this on every run.
+	print(datetime.datetime.utcnow().strftime("%a %b %d %H:%M:%S UTC %Y"))
+
+	parser = argparse.ArgumentParser(description="A Python-based TO_ORT implementation",
+	                                 epilog="Doesn't support the 'TRACE' or 'NONE' log levels.",
+	                                 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+	parser.add_argument("Mode",
+	                    help="REPORT: Do nothing, but print what would be done\n"\
+	                         "")
+	parser.add_argument("Log_Level",
+	                    help="ALL, DEBUG, INFO, WARN, ERROR, FATAL",
+	                    type=str)
+	parser.add_argument("Traffic_Ops_URL",
+	                    help="URL to Traffic Ops host. Example: https://trafficops.company.net",
+	                    type=str)
+	parser.add_argument("Traffic_Ops_Login",
+	                    help="Example: 'username:password'")
+	parser.add_argument("--dispersion",
+	                    help="wait a random number between 0 and <dispersion> before starting.",
+	                    type=int,
+	                    default=300)
+	parser.add_argument("--login_dispersion",
+	                    help="wait a random number between 0 and <login_dispersion> before login.",
+	                    type=int,
+	                    default=0)
+	parser.add_argument("--retries",
+	                    help="retry connection to Traffic Ops URL <retries> times.",
+	                    type=int,
+	                    default=3)
+	parser.add_argument("--wait_for_parents",
+	                    help="do not update if parent_pending = 1 in the update json.",
+	                    type=int,
+	                    default=1)
+	parser.add_argument("--rev_proxy_disabled",
+	                    help="bypass the reverse proxy even if one has been configured.",
+	                    type=int,
+	                    default=0)
+	parser.add_argument("--ts_root",
+	                    help="Specify the root directory at which Apache Traffic Server is installed"\
+	                         " (e.g. '/opt/trafficserver')",
+	                    type=str,
+	                    default="/")
+	parser.add_argument("-v", "--version",
+	                    action="version",
+	                    version="%(prog)s v"+__version__,
+	                    help="Print version information and exit.")
+
+	args = parser.parse_args()
+
+	logLevel = args.Log_Level.upper()
+	if logLevel not in LOG_LEVELS:
+		print("Unrecognized/Unsupported log level:", args.Log_Level, file=sys.stderr)
+		return 1
+
+	logging.basicConfig(level=LOG_LEVELS[logLevel], format=FMT)
+	logging.getLogger().setLevel(LOG_LEVELS[logLevel])
+
+	try:
+		MODE = Modes[args.Mode.upper()]
+	except KeyError as e:
+		logging.critical("Unknown mode '%s'", args.Mode)
+		logging.debug("%s", e, exc_info=True, stack_info=True)
+		return 1
+
+	logging.info("Running in %s mode", MODE)
+
+	if needInstall:
+		if not handleMissingPythonPackages(needInstall):
+			return 1
+
+	tsroot = args.ts_root.strip()
+	tsbin = os.path.join(tsroot, "bin", "traffic_server")
+	if not os.path.isdir(tsroot) or not os.path.isfile(tsbin):
+		logging.critical("Unable to find root Apache Traffic Server installation")
+		logging.error("No such directory: '%s' or no such file: '%s", tsroot, tsbin)
+		return 1
+
+	TS_ROOT = args.ts_root
+	logging.info("Traffic Server root is at %s", TS_ROOT)
+
+	logging.info("Distro detected as '%s'", DISTRO)
+
+	logging.info("Hostname detected as '%s'", HOSTNAME[1])
+
+	TO_URL = args.Traffic_Ops_URL.rstrip('/')
+
+	logging.info("Traffic_Ops_URL: %s", TO_URL)
+
+	if not TO_URL.startswith("http"):
+		logging.critical("Malformed Traffic_Ops_URL: '%s' - "\
+		                 "must include scheme (e.g. http://traffic.ops)", TO_URL)
+		return 1
+
+	# Litmus test to make sure the server exists and can be reached
+	try:
+		_ = requests.head(TO_URL, verify=False)
+	except requests.exception as e:
+		logging.critical("Malformed or Invalid Traffic_Ops_URL")
+		logging.error("%s", e)
+		logging.debug("%s", e, exc_info=True, stack_info=True)
+		return 1
+
+	try:
+		TO_LOGIN = setTO_LOGIN(args.Traffic_Ops_Login)
+	except IndexError:
+		logging.critical("Bad Traffic_Ops_Login: '%s' - should be like 'username:password'")
+		return 1
+	except PermissionError:
+		logging.critical("Failed to obtain cookied from Traffic Ops")
+		return 1
+
+	return doMain()
+
+
+if __name__ == '__main__':
+	try:
+		exit(main())
+	except requests.exceptions.ConnectionError as e:
+		print("Failed to connect to Traffic Ops:", e, file=sys.stderr)
+		exit(1)
diff --git a/infrastructure/cdn-in-a-box/enroller/Dockerfile b/infrastructure/cdn-in-a-box/enroller/Dockerfile
index cb6bf01b9..ae1ed4cd9 100644
--- a/infrastructure/cdn-in-a-box/enroller/Dockerfile
+++ b/infrastructure/cdn-in-a-box/enroller/Dockerfile
@@ -15,19 +15,28 @@
 # specific language governing permissions and limitations
 # under the License.
 
-FROM golang:1.10
+FROM golang:1.11 AS enroller-builder
 
-EXPOSE 443
-
-COPY ./infrastructure/cdn-in-a-box/ /go/src/github.com/apache/trafficcontrol/infrastructure/cdn-in-a-box/
-
-# enroller dependencies are here
+# enroller source and dependencies
 COPY ./lib/ /go/src/github.com/apache/trafficcontrol/lib/
 COPY ./vendor/ /go/src/github.com/apache/trafficcontrol/vendor/
 COPY ./traffic_ops/client/ /go/src/github.com/apache/trafficcontrol/traffic_ops/client/
 COPY ./traffic_ops/vendor/ /go/src/github.com/apache/trafficcontrol/traffic_ops/vendor/
+COPY ./infrastructure/cdn-in-a-box/enroller/ /go/src/github.com/apache/trafficcontrol/infrastructure/cdn-in-a-box/enroller/
 
 WORKDIR /go/src/github.com/apache/trafficcontrol/infrastructure/cdn-in-a-box/enroller
+RUN go get -v && go build
+
+COPY ./infrastructure/cdn-in-a-box/ /go/src/github.com/apache/trafficcontrol/infrastructure/cdn-in-a-box/
+COPY ./infrastructure/cdn-in-a-box/enroller/server_template.json /
 
-RUN go get -v
+
+FROM debian:stretch
+
+RUN apt-get update && apt-get install -y netcat curl && apt-get clean
+COPY --from=enroller-builder \
+   /go/src/github.com/apache/trafficcontrol/infrastructure/cdn-in-a-box \
+   /go/src/github.com/apache/trafficcontrol/infrastructure/cdn-in-a-box
+
+WORKDIR /go/src/github.com/apache/trafficcontrol/infrastructure/cdn-in-a-box/enroller
 CMD ./run.sh
diff --git a/infrastructure/cdn-in-a-box/enroller/enroller.go b/infrastructure/cdn-in-a-box/enroller/enroller.go
index 3efcdd84c..1341cd1c2 100644
--- a/infrastructure/cdn-in-a-box/enroller/enroller.go
+++ b/infrastructure/cdn-in-a-box/enroller/enroller.go
@@ -19,43 +19,29 @@ package main
 
 import (
 	"bytes"
-	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io"
 	"log"
-	"net"
-	"net/http"
+	"net/url"
 	"os"
 	"strings"
 	"time"
 
-	"github.com/apache/trafficcontrol/lib/go-tc/v13"
-	clientv13 "github.com/apache/trafficcontrol/traffic_ops/client/v13"
-	dockertypes "github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/network"
-	dockerclient "github.com/docker/docker/client"
+	tc "github.com/apache/trafficcontrol/lib/go-tc"
+	client "github.com/apache/trafficcontrol/traffic_ops/client"
 	"github.com/kelseyhightower/envconfig"
+	"gopkg.in/fsnotify.v1"
 )
 
 type session struct {
-	*clientv13.Session
-	*dockerclient.Client
-	addr net.Addr
+	*client.Session
 }
 
-func newSession(reqTimeout time.Duration, toURL string, toUser string, toPass string) (*session, error) {
-	s, addr, err := clientv13.LoginWithAgent(toURL, toUser, toPass, true, "cdn-in-a-box-enroller", true, reqTimeout)
-	if err != nil {
-		return nil, err
-	}
-
-	dockerCli, err := dockerclient.NewClientWithOpts(dockerclient.WithVersion("1.38"), dockerclient.FromEnv)
-	if err != nil {
-		return nil, err
-	}
-
-	return &session{Session: s, addr: addr, Client: dockerCli}, err
+func newSession(reqTimeout time.Duration, toURL string, toUser string, toPass string) (session, error) {
+	s, _, err := client.LoginWithAgent(toURL, toUser, toPass, true, "cdn-in-a-box-enroller", true, reqTimeout)
+	return session{s}, err
 }
 
 func printJSON(label string, b interface{}) {
@@ -66,295 +52,1191 @@ func printJSON(label string, b interface{}) {
 	fmt.Println(label, buf.String())
 }
 
-func (s *session) getExposedPorts(c dockertypes.ContainerJSON) []int {
-	var ports []int
-	for port := range c.Config.ExposedPorts {
-		ports = append(ports, port.Int())
+// TODO: Some GetxxxByxxx() methods escape the string passed in; others don't
+//  Here we escape the name if not escaped in the Getxxx method being called
+func (s session) getTypeIDByName(n string) (int, error) {
+	types, _, err := s.GetTypeByName(url.QueryEscape(n))
+	if err != nil {
+		return -1, err
 	}
-	return ports
+	if len(types) == 0 {
+		return -1, errors.New("no type with name " + n)
+	}
+	return types[0].ID, err
 }
 
-func (s *session) getNetwork(c dockertypes.ContainerJSON) (*network.EndpointSettings, error) {
-	if c.NetworkSettings == nil {
-		return nil, errors.New("cannot get network from container")
+func (s session) getCoordinateIDByName(n string) (int, error) {
+	coordinates, _, err := s.GetCoordinateByName(url.QueryEscape(n))
+	if err != nil {
+		return -1, err
 	}
-	mode := string(c.HostConfig.NetworkMode)
-	net, ok := c.NetworkSettings.Networks[mode]
-	if !ok {
-		return nil, errors.New("no network for " + mode)
+	if len(coordinates) == 0 {
+		return -1, errors.New("no coordinate with name " + n)
 	}
-	return net, nil
+	return coordinates[0].ID, err
 }
 
-// Matches service name (container) with type in traffic ops db
-var serviceTypes = map[string]string{
-	"db":               "TRAFFIC_OPS_DB",
-	"edge":             "EDGE",
-	"influxdb":         "INFLUXDB",
-	"mid":              "MID",
-	"origin":           "ORG",
-	"trafficanalytics": "TRAFFIC_ANALYTICS",
-	"trafficmonitor":   "RASCAL",
-	"trafficops":       "TRAFFIC_OPS",
-	"trafficops-perl":  "TRAFFIC_OPS",
-	"trafficportal":    "TRAFFIC_PORTAL",
-	"trafficrouter":    "CCR",
-	"trafficstats":     "TRAFFIC_STATS",
-	"trafficvault":     "RIAK",
+func (s session) getCDNIDByName(n string) (int, error) {
+	cdns, _, err := s.GetCDNByName(url.QueryEscape(n))
+	if err != nil {
+		return -1, err
+	}
+	if len(cdns) == 0 {
+		return -1, errors.New("no CDN with name " + n)
+	}
+	return cdns[0].ID, err
 }
 
-func containerName(c dockertypes.ContainerJSON) string {
-	return strings.Trim(c.Name, "/")
+func (s session) getRegionIDByName(n string) (int, error) {
+	divisions, _, err := s.GetRegionByName(url.QueryEscape(n))
+	if err != nil {
+		return -1, err
+	}
+	if len(divisions) == 0 {
+		return -1, errors.New("no division with name " + n)
+	}
+	return divisions[0].ID, err
 }
 
-func serviceName(c dockertypes.ContainerJSON) string {
-	if s, ok := c.Config.Labels["com.docker.compose.service"]; ok {
-		return s
+func (s session) getDivisionIDByName(n string) (int, error) {
+	divisions, _, err := s.GetDivisionByName(url.QueryEscape(n))
+	if err != nil {
+		return -1, err
 	}
-	return containerName(c)
+	if len(divisions) == 0 {
+		return -1, errors.New("no division with name " + n)
+	}
+	return divisions[0].ID, err
 }
 
-func serverType(serviceName string) string {
-	for s, t := range serviceTypes {
-		if s == serviceName {
-			return t
-		}
+func (s session) getPhysLocationIDByName(n string) (int, error) {
+	physLocs, _, err := s.GetPhysLocationByName(url.QueryEscape(n))
+	if err != nil {
+		return -1, err
 	}
-	// unknown -- let caller deal with it
-	return serviceName
+	if len(physLocs) == 0 {
+		return -1, errors.New("no physLocation with name " + n)
+	}
+	return physLocs[0].ID, err
 }
 
-func (s *session) getTypeIDByName(typeName string) (int, error) {
-	types, _, err := s.GetTypeByName(typeName)
-	if err != nil || len(types) == 0 {
-		fmt.Printf("unknown type %s\n", typeName)
+func (s session) getCachegroupIDByName(n string) (int, error) {
+	cgs, _, err := s.GetCacheGroupByName(url.QueryEscape(n))
+	if err != nil {
 		return -1, err
 	}
-	fmt.Printf("type %s: %++v\n", typeName, types)
-	return types[0].ID, err
+	if len(cgs) == 0 {
+		return -1, errors.New("no cachegroups with name" + n)
+	}
+	return cgs[0].ID, err
 }
 
-func (s *session) getCDNIDByName(name string) (int, error) {
-	cdns, _, err := s.GetCDNByName(name)
+func (s session) getProfileIDByName(n string) (int, error) {
+	profiles, _, err := s.GetProfileByName(n)
 	if err != nil {
-		fmt.Println("cannot get CDNS")
 		return -1, err
 	}
-	if len(cdns) < 1 {
-		panic(fmt.Sprintf("CDNS: %v;  err: %v", cdns, err))
+	if len(profiles) == 0 {
+		return -1, errors.New("no profile with name " + n)
 	}
-	return cdns[0].ID, err
+	return profiles[0].ID, err
 }
 
-func (s *session) getCachegroupID() (int, error) {
-	cgs, _, err := s.GetCacheGroups()
+func (s session) getParameterIDMatching(m tc.Parameter) (int, error) {
+	// TODO: s.GetParameterByxxx() does not seem to work with values with spaces --
+	// doing this the hard way for now
+	parameters, _, err := s.GetParameters()
 	if err != nil {
-		fmt.Println("cannot get Cachegroup")
 		return -1, err
 	}
-	if len(cgs) == 0 {
-		return -1, errors.New("No cachegroups found")
+	for _, p := range parameters {
+		if p.Name == m.Name && p.Value == m.Value && p.ConfigFile == m.ConfigFile {
+			return p.ID, nil
+		}
 	}
-	return cgs[0].ID, err
+	return -1, fmt.Errorf("no parameter matching name %s, configFile %s, value %s", m.Name, m.ConfigFile, m.Value)
 }
 
-func (s *session) getPhysLocationID() (int, error) {
-	physLocs, _, err := s.GetPhysLocations()
+func (s session) getStatusIDByName(n string) (int, error) {
+	statuses, _, err := s.GetStatusByName(url.QueryEscape(n))
 	if err != nil {
-		fmt.Println("cannot get physlocations")
 		return -1, err
 	}
-	return physLocs[0].ID, err
+	if len(statuses) == 0 {
+		return -1, errors.New("no status with name " + n)
+	}
+	return statuses[0].ID, err
 }
 
-func (s *session) getProfileID() (int, error) {
-	profiles, _, err := s.GetProfiles()
+func (s session) getRoleIDByName(n string) (int, error) {
+	roles, _, _, err := s.GetRoleByName(url.QueryEscape(n))
 	if err != nil {
-		fmt.Println("cannot get profiles")
 		return -1, err
 	}
-	return profiles[0].ID, err
+	if len(roles) == 0 || roles[0].ID == nil {
+		return -1, errors.New("no role with name " + n)
+	}
+	return *roles[0].ID, err
 }
 
-func (s *session) getStatusIDByName(cdnName string) (int, error) {
-	statuses, _, err := s.GetStatusByName(cdnName)
+func (s session) getServerIDByHostName(n string) (int, error) {
+	servers, _, err := s.GetServerByHostName(url.QueryEscape(n))
 	if err != nil {
-		fmt.Printf("unknown Status %s\n", cdnName)
 		return -1, err
 	}
-	return statuses[0].ID, err
+	if len(servers) == 0 {
+		return -1, errors.New("no server with hostName " + n)
+	}
+	return servers[0].ID, err
 }
 
-func getMask(m []byte) string {
-	return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
+func (s session) getDeliveryServiceIDByXMLID(n string) (int, error) {
+	dses, _, err := s.GetDeliveryServiceByXMLID(url.QueryEscape(n))
+	if err != nil {
+		return -1, err
+	}
+	if len(dses) == 0 {
+		return -1, errors.New("no deliveryservice with name " + n)
+	}
+	return dses[0].ID, err
 }
 
-func (s *session) enrollContainer(c dockertypes.ContainerJSON) (*v13.Server, error) {
-	hostName := serviceName(c)
-	cName := containerName(c)
-	fmt.Printf("enrolling %s(%s)\n", cName, hostName)
-	server := v13.Server{
-		HostName:   hostName,
-		DomainName: os.Getenv("DOMAINNAME"),
+func (s session) getTenantIDByName(n string) (int, error) {
+	tenant, _, err := s.TenantByName(url.QueryEscape(n))
+	if err != nil {
+		return -1, err
 	}
+	if tenant == nil {
+		return -1, errors.New("no tenant with name " + n)
+	}
+	return tenant.ID, err
+}
 
-	fmt.Println("type is ", serverType(hostName))
-	fmt.Println("hostName is ", hostName)
-	var err error
-	server.TypeID, err = s.getTypeIDByName(serverType(hostName))
+var to struct {
+	URL      string `envconfig:"TO_URL"`
+	User     string `envconfig:"TO_USER"`
+	Password string `envconfig:"TO_PASSWORD"`
+}
+
+// enrollType takes a json file and creates a Type object using the TO API
+func enrollType(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
 	if err != nil {
-		fmt.Printf("cannot get type for %s", hostName)
+		return err
 	}
+	defer func() {
+		fh.Close()
+	}()
 
-	server.StatusID, err = s.getStatusIDByName("PRE_PROD")
+	dec := json.NewDecoder(fh)
+	var s tc.Type
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	alerts, _, err := toSession.CreateType(s)
 	if err != nil {
-		fmt.Printf("cannot get status for %s", hostName)
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("type %s already exists\n", s.Name)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
 	}
 
-	server.CDNID, err = s.getCDNIDByName("ALL")
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+// enrollCDN takes a json file and creates a CDN object using the TO API
+func enrollCDN(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
 	if err != nil {
-		fmt.Printf("cannot get CDN for %s", hostName)
+		return err
 	}
+	defer func() {
+		fh.Close()
+	}()
 
-	server.ProfileID, err = s.getProfileID()
+	dec := json.NewDecoder(fh)
+	var s tc.CDN
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	alerts, _, err := toSession.CreateCDN(s)
 	if err != nil {
-		fmt.Printf("cannot get profile for %s", hostName)
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("cdn %s already exists\n", s.Name)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
 	}
 
-	server.CachegroupID, err = s.getCachegroupID()
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+func enrollASN(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
 	if err != nil {
-		fmt.Printf("cannot get Cachegroup for %s", cName)
+		return err
 	}
+	defer func() {
+		fh.Close()
+	}()
 
-	server.PhysLocationID, err = s.getPhysLocationID()
+	dec := json.NewDecoder(fh)
+	var s tc.ASN
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	alerts, _, err := toSession.CreateASN(s)
 	if err != nil {
-		fmt.Printf("cannot get PhysLocation for %s", cName)
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("asn %d already exists\n", s.ASN)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
 	}
-	dnet, err := s.getNetwork(c)
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+// enrollCachegroup takes a json file and creates a Cachegroup object using the TO API
+func enrollCachegroup(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
 	if err != nil {
-		fmt.Printf("cannot get network: %v\n", err)
-		return nil, err
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.CacheGroupNullable
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
 	}
 
-	server.IPAddress = dnet.IPAddress
-	server.IPNetmask = getMask(net.CIDRMask(dnet.IPPrefixLen, net.IPv4len*8))
-	server.IPGateway = dnet.Gateway
-	server.IP6Address = dnet.GlobalIPv6Address
-	server.IP6Gateway = dnet.IPv6Gateway
+	if s.Type != nil {
+		id, err := toSession.getTypeIDByName(*s.Type)
+		if err != nil {
+			return err
+		}
+		s.TypeID = &id
+	}
 
-	ports := s.getExposedPorts(c)
+	if s.ParentName != nil {
+		id, err := toSession.getCachegroupIDByName(*s.ParentName)
+		if err != nil {
+			return err
+		}
+		s.ParentCachegroupID = &id
+	}
+
+	if s.SecondaryParentName != nil {
+		id, err := toSession.getCachegroupIDByName(*s.SecondaryParentName)
+		if err != nil {
+			return err
+		}
+		s.SecondaryParentCachegroupID = &id
+	}
+
+	alerts, _, err := toSession.CreateCacheGroupNullable(s)
 	if err != nil {
-		fmt.Printf("cannot get exposed ports: %v\n", err)
-		return nil, err
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("cachegroup %s already exists\n", *s.Name)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
 	}
 
-	if len(ports) > 0 {
-		// TODO: for now, assuming there's only 1
-		server.TCPPort = ports[0]
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+func enrollDeliveryService(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.DeliveryService
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	if s.Type != "" {
+		id, err := toSession.getTypeIDByName(s.Type.String())
+		if err != nil {
+			return err
+		}
+		s.TypeID = id
+	}
+
+	if s.CDNName != "" {
+		id, err := toSession.getCDNIDByName(s.CDNName)
+		if err != nil {
+			return err
+		}
+		s.CDNID = id
+	}
+
+	if s.ProfileName != "" {
+		id, err := toSession.getProfileIDByName(s.ProfileName)
+		if err != nil {
+			return err
+		}
+		s.ProfileID = id
+	}
+
+	if s.TenantName != "" {
+		id, err := toSession.getTenantIDByName(s.TenantName)
+		if err != nil {
+			return err
+		}
+		s.TenantID = id
+	}
+
+	alerts, err := toSession.CreateDeliveryService(&s)
+	if err != nil {
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("deliveryservice %s already exists\n", s.XMLID)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
 	}
-	var buf bytes.Buffer
-	enc := json.NewEncoder(&buf)
-	enc.SetIndent(``, `  `)
-	enc.Encode(server)
-	fmt.Println("Server: ", buf.String())
 
-	resp, _, err := s.CreateServer(server)
-	fmt.Printf("Response: %s\n", resp)
-	return &server, err
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
 }
 
-var to struct {
-	URL      string `envconfig:"TO_URL"`
-	User     string `envconfig:"TO_USER"`
-	Password string `envconfig:"TO_PASSWORD"`
+func enrollDeliveryServiceServer(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+
+	// DeliveryServiceServers lists ds xmlid and array of server names.  Use that to create multiple DeliveryServiceServer objects
+	var dss tc.DeliveryServiceServers
+	err = dec.Decode(&dss)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	dsID, err := toSession.getDeliveryServiceIDByXMLID(dss.XmlId)
+	if err != nil {
+		return err
+	}
+
+	var serverIDs []int
+	for _, sn := range dss.ServerNames {
+		id, err := toSession.getServerIDByHostName(sn)
+		if err != nil {
+			log.Println("error finding " + sn + ": " + err.Error())
+			continue
+		}
+		serverIDs = append(serverIDs, id)
+	}
+	_, err = toSession.CreateDeliveryServiceServers(dsID, serverIDs, true)
+	if err != nil {
+		log.Printf("error creating from %s: %s\n", fn, err)
+	}
+
+	return err
 }
 
-func (s *session) enrollerHandler() func(http.ResponseWriter, *http.Request) {
-	return func(w http.ResponseWriter, r *http.Request) {
-		if err := r.ParseForm(); err != nil {
-			fmt.Fprintf(w, "ParseForm() err: %v", err)
-			return
+func enrollDivision(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.Division
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	alerts, _, err := toSession.CreateDivision(s)
+	if err != nil {
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("division %s already exists\n", s.Name)
+			return nil
 		}
-		hostName := r.FormValue("host")
-		cName := r.FormValue("name")
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
+	}
 
-		match := func(dockertypes.ContainerJSON) bool { return true }
-		switch {
-		case len(hostName) > 0:
-			match = func(c dockertypes.ContainerJSON) bool {
-				return hostName == c.Name
-			}
-		case len(cName) > 0:
-			match = func(c dockertypes.ContainerJSON) bool {
-				return cName == c.Config.Hostname
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+func enrollOrigin(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.Origin
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	if s.Cachegroup != nil && *s.Cachegroup != "" {
+		id, err := toSession.getCachegroupIDByName(*s.Cachegroup)
+		if err != nil {
+			return err
+		}
+		s.CachegroupID = &id
+	}
+
+	if s.DeliveryService != nil && *s.DeliveryService != "" {
+		id, err := toSession.getDeliveryServiceIDByXMLID(*s.DeliveryService)
+		if err != nil {
+			return err
+		}
+		s.DeliveryServiceID = &id
+	}
+
+	if s.Profile != nil && *s.Profile != "" {
+		id, err := toSession.getProfileIDByName(*s.Profile)
+		if err != nil {
+			return err
+		}
+		s.ProfileID = &id
+	}
+
+	if s.Coordinate != nil && *s.Coordinate != "" {
+		id, err := toSession.getCoordinateIDByName(*s.Coordinate)
+		if err != nil {
+			return err
+		}
+		s.CoordinateID = &id
+	}
+
+	if s.Tenant != nil && *s.Tenant != "" {
+		id, err := toSession.getTenantIDByName(*s.Tenant)
+		if err != nil {
+			return err
+		}
+		s.TenantID = &id
+	}
+
+	alerts, _, err := toSession.CreateOrigin(s)
+	if err != nil {
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("origin %s already exists\n", *s.Name)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
+	}
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+func enrollParameter(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.Parameter
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+	paramID, err := toSession.getParameterIDMatching(s)
+	var alerts tc.Alerts
+	if err == nil {
+		// existing param -- update
+		alerts, _, err = toSession.UpdateParameterByID(paramID, s)
+		if err != nil {
+			log.Printf("error updating parameter %d: %s with %+v ", paramID, err.Error(), s)
+		}
+	} else {
+		alerts, _, err = toSession.CreateParameter(s)
+		if err != nil {
+			if strings.Contains(err.Error(), "already exists") {
+				log.Printf("parameter %s already exists\n", s.Name)
+				return nil
 			}
+			log.Printf("error creating from %s: %s\n", fn, err)
+			return err
+		}
+	}
+	// link parameter with profiles
+	if len(s.Profiles) > 0 {
+		paramID, err := toSession.getParameterIDMatching(s)
+		if err != nil {
+			return err
 		}
 
-		net, err := s.NetworkInspect(context.Background(), "cdn-in-a-box_tcnet", dockertypes.NetworkInspectOptions{})
+		var profiles []string
+		err = json.Unmarshal(s.Profiles, &profiles)
 		if err != nil {
-			http.Error(w, err.Error(), http.StatusBadRequest)
-			return
+			log.Printf("%v", err)
 		}
-		var names []string
-		var containers []dockertypes.ContainerJSON
-		for _, epr := range net.Containers {
-			c, err := s.ContainerInspect(context.Background(), epr.Name)
+
+		for _, n := range profiles {
+			pid, err := toSession.getProfileIDByName(n)
 			if err != nil {
-				http.Error(w, err.Error(), http.StatusBadRequest)
-				return
+				log.Printf("%v", err)
+				continue
 			}
-
-			// check against any query params provided
-			if !match(c) {
+			pp := tc.ProfileParameter{ParameterID: paramID, ProfileID: pid}
+			_, _, err = toSession.CreateProfileParameter(pp)
+			if err != nil {
+				if strings.Contains(err.Error(), "already exists") {
+					continue
+				}
+				log.Printf("%v", err)
 				continue
 			}
+		}
+	}
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
 
-			fmt.Printf("including %s\n", c.Name)
-			names = append(names, c.Name)
-			containers = append(containers, c)
+func enrollPhysLocation(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.PhysLocation
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	if s.RegionName != "" {
+		id, err := toSession.getRegionIDByName(s.RegionName)
+		if err != nil {
+			return err
 		}
+		s.RegionID = id
+	}
+	alerts, _, err := toSession.CreatePhysLocation(s)
+	if err != nil {
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("physLocation %s already exists\n", s.Name)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
+	}
 
-		switch r.Method {
-		case "GET":
-			// just list the container names
-			enc := json.NewEncoder(w)
-			enc.Encode(names)
-			return
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
 
-		case "POST":
-			// enroll each container
-			var servers []*v13.Server
-			for _, c := range containers {
-				server, err := s.enrollContainer(c)
-				if err != nil {
-					fmt.Printf("failed to enroll %s\n", containerName(c))
-					continue
+	return err
+}
+
+func enrollRegion(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.Region
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	if s.DivisionName != "" {
+		id, err := toSession.getDivisionIDByName(s.DivisionName)
+		if err != nil {
+			return err
+		}
+		s.Division = id
+	}
+
+	alerts, _, err := toSession.CreateRegion(s)
+	if err != nil {
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("region %s already exists\n", s.Name)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
+	}
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+func enrollStatus(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.Status
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	alerts, _, err := toSession.CreateStatus(s)
+	if err != nil {
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("status %s already exists\n", s.Name)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
+	}
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+func enrollTenant(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.Tenant
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	if s.ParentName != "" {
+		id, err := toSession.getTenantIDByName(s.ParentName)
+		if err != nil {
+			return err
+		}
+		s.ParentID = id
+	}
+
+	alerts, err := toSession.CreateTenant(&s)
+	if err != nil {
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("tenant %s already exists\n", s.Name)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
+	}
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+func enrollUser(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.User
+	err = dec.Decode(&s)
+	log.Printf("User is %++v\n", s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	if s.Tenant != nil && *s.Tenant != "" {
+		id, err := toSession.getTenantIDByName(*s.Tenant)
+		if err != nil {
+			return err
+		}
+		s.TenantID = &id
+	}
+
+	if s.RoleName != nil && *s.RoleName != "" {
+		id, err := toSession.getRoleIDByName(*s.RoleName)
+		if err != nil {
+			return err
+		}
+		s.Role = &id
+	}
+
+	alerts, _, err := toSession.CreateUser(&s)
+	if err != nil {
+		if strings.Contains(err.Error(), "already exists") {
+			log.Printf("user %s already exists\n", *s.Username)
+			return nil
+		}
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
+	}
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+// using documented import form for profiles
+type profileImport struct {
+	Profile    tc.Profile             `json:"profile"`
+	Parameters []tc.ParameterNullable `json:"parameters"`
+}
+
+// enrollProfile takes a json file and creates a Profile object using the TO API
+func enrollProfile(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var profile tc.Profile
+
+	err = dec.Decode(&profile)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("  ", "")
+	enc.Encode(profile)
+
+	if len(profile.Name) == 0 {
+		log.Println("missing name on profile from " + fn)
+		return errors.New("missing name on profile from " + fn)
+	}
+
+	profiles, _, err := toSession.GetProfileByName(profile.Name)
+
+	createProfile := false
+	if err != nil || len(profiles) == 0 {
+		// no profile by that name -- need to create it
+		createProfile = true
+	} else {
+		// updating - ID needs to match
+		profile.ID = profiles[0].ID
+	}
+
+	// these need to be done whether creating or updating
+	if profile.CDNName != "" {
+		id, err := toSession.getCDNIDByName(profile.CDNName)
+		if err != nil {
+			return err
+		}
+		profile.CDNID = id
+	}
+
+	var alerts tc.Alerts
+	var action string
+	if createProfile {
+		alerts, _, err = toSession.CreateProfile(profile)
+		if err != nil {
+			if strings.Contains(err.Error(), "already exists") {
+				log.Printf("profile %s already exists\n", profile.Name)
+			} else {
+				log.Printf("error creating profile from %+v: %s\n", profile, err.Error())
+			}
+		}
+		profile.ID, err = toSession.getProfileIDByName(profile.Name)
+		if err != nil {
+			log.Printf("error getting profile ID from %+v: %s\n", profile, err.Error())
+		}
+		action = "creating"
+	} else {
+		alerts, _, err = toSession.UpdateProfileByID(profile.ID, profile)
+		action = "updating"
+	}
+
+	if err != nil {
+		log.Printf("error "+action+" from %s: %s\n", fn, err)
+		return err
+	}
+
+	//log.Printf("total profile is  %+v\n", profile)
+	for _, p := range profile.Parameters {
+		var name, configFile, value string
+		var secure bool
+		if p.ConfigFile != nil {
+			configFile = *p.ConfigFile
+		}
+		if p.Name != nil {
+			name = *p.Name
+		}
+		if p.Value != nil {
+			value = *p.Value
+		}
+		param := tc.Parameter{ConfigFile: configFile, Name: name, Value: value, Secure: secure}
+		log.Printf("creating param %+v\n", param)
+		id, err := toSession.getParameterIDMatching(param)
+		if err != nil {
+			// create it
+			_, _, err = toSession.CreateParameter(param)
+			if err != nil {
+				if !strings.Contains(err.Error(), "already exists") {
+					log.Printf("can't create parameter %+v: %s\n", param, err.Error())
 				}
-				servers = append(servers, server)
+				continue
 			}
-			enc := json.NewEncoder(w)
-			if err := enc.Encode(servers); err != nil {
-				fmt.Println("failed to encode servers")
+			param.ID, err = toSession.getParameterIDMatching(param)
+			if err != nil {
+				log.Printf("error getting new parameter %+v\n", param)
+				param.ID, err = toSession.getParameterIDMatching(param)
+				log.Printf(err.Error())
+
 			}
-			return
+		} else {
+			param.ID = id
+			toSession.UpdateParameterByID(param.ID, param)
+		}
 
-		default:
-			http.Error(w, "unhandled method "+r.Method, http.StatusBadRequest)
+		if param.ID < 1 {
+			panic(fmt.Sprintf("param ID not found for %v", param))
+
+		}
+		pp := tc.ProfileParameter{ProfileID: profile.ID, ParameterID: param.ID}
+		_, _, err = toSession.CreateProfileParameter(pp)
+		if err != nil {
+			if !strings.Contains(err.Error(), "already exists") {
+				log.Printf("error creating profileparameter %+v: %s\n", pp, err.Error())
+			}
+			continue
+		}
+	}
+
+	//enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+// enrollServer takes a json file and creates a Server object using the TO API
+func enrollServer(toSession *session, fn string) error {
+	fh, err := os.Open(fn)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		fh.Close()
+	}()
+
+	dec := json.NewDecoder(fh)
+	var s tc.Server
+	err = dec.Decode(&s)
+	if err != nil && err != io.EOF {
+		log.Printf("error decoding %s: %s\n", fn, err)
+		return err
+	}
+
+	if s.Type != "" {
+		id, err := toSession.getTypeIDByName(s.Type)
+		if err != nil {
+			return err
+		}
+		s.TypeID = id
+	}
+
+	if s.Profile != "" {
+		id, err := toSession.getProfileIDByName(s.Profile)
+		if err != nil {
+			return err
+		}
+		s.ProfileID = id
+	}
+
+	if s.Status != "" {
+		id, err := toSession.getStatusIDByName(s.Status)
+		if err != nil {
+			return err
+		}
+		s.StatusID = id
+	}
+	if s.CDNName != "" {
+		id, err := toSession.getCDNIDByName(s.CDNName)
+		if err != nil {
+			return err
+		}
+		s.CDNID = id
+	}
+	if s.Cachegroup != "" {
+		id, err := toSession.getCachegroupIDByName(s.Cachegroup)
+		if err != nil {
+			return err
+		}
+		s.CachegroupID = id
+	}
+	if s.PhysLocation != "" {
+		id, err := toSession.getPhysLocationIDByName(s.PhysLocation)
+		if err != nil {
+			return err
+		}
+		s.PhysLocationID = id
+	}
+
+	alerts, _, err := toSession.CreateServer(s)
+	if err != nil {
+		log.Printf("error creating from %s: %s\n", fn, err)
+		return err
+	}
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetIndent("", "  ")
+	err = enc.Encode(&alerts)
+
+	return err
+}
+
+type dirWatcher struct {
+	*fsnotify.Watcher
+	TOSession *session
+	watched   map[string]func(toSession *session, fn string) error
+}
+
+func newDirWatcher(toSession *session) (*dirWatcher, error) {
+	var err error
+	var dw dirWatcher
+	dw.Watcher, err = fsnotify.NewWatcher()
+	if err != nil {
+		return nil, err
+	}
+	dw.watched = make(map[string]func(toSession *session, fn string) error)
+	go func() {
+		const (
+			processed = ".processed"
+			rejected  = ".rejected"
+		)
+
+		for {
+			select {
+			case event, ok := <-dw.Events:
+				if !ok {
+					log.Printf("event not ok: %+v", event)
+					return
+				}
+
+				//log.Println("event:", event)
+				if event.Op&fsnotify.Create == fsnotify.Create {
+					if strings.HasSuffix(event.Name, processed) || strings.HasSuffix(event.Name, rejected) {
+						continue
+					}
+					i, err := os.Stat(event.Name)
+					if err != nil || i.IsDir() {
+						log.Println("skipping " + event.Name)
+						continue
+					}
+					log.Println("new file :", event.Name)
+					p := strings.IndexRune(event.Name, '/')
+					if p == -1 {
+						continue
+					}
+					dir := event.Name[:p]
+					suffix := rejected
+					if f, ok := dw.watched[dir]; ok {
+						log.Printf("creating from %s\n", event.Name)
+						// TODO: ensure file content is there before attempting to read
+						time.Sleep(100 * time.Millisecond)
+
+						err := f(toSession, event.Name)
+						if err != nil {
+							log.Printf("error creating %s from %s: %s\n", dir, event.Name, err.Error())
+						} else {
+							suffix = processed
+						}
+					} else {
+						log.Printf("no method for creating %s\n", dir)
+					}
+					// rename the file indicating if processed or rejected
+					err = os.Rename(event.Name, event.Name+suffix)
+					if err != nil {
+						log.Printf("error renaming %s to %s: %s\n", event.Name, event.Name+suffix, err.Error())
+					}
+				}
+			case err, ok := <-dw.Errors:
+				if !ok {
+					log.Printf("error not ok: %+v", err)
+				}
+				log.Println("error:", err)
+			}
+		}
+	}()
+	return &dw, err
+}
+
+// watch starts f when a new file is created in dir
+func (dw *dirWatcher) watch(dir string, f func(*session, string) error) {
+	if stat, err := os.Stat(dir); err != nil || !stat.IsDir() {
+		// attempt to create dir
+		if err = os.Mkdir(dir, os.ModeDir|0700); err != nil {
+			log.Println("cannot watch " + dir + ": not a directory")
 			return
 		}
 	}
+	log.Println("watching " + dir)
+	dw.Add(dir)
+	dw.watched[dir] = f
 }
 
+const startedFile = "enroller-started"
+
 func main() {
+	watchDir := "."
+	if len(os.Args) > 1 {
+		watchDir = os.Args[1]
+	}
+	if stat, err := os.Stat(watchDir); err != nil || !stat.IsDir() {
+		log.Fatalln("expected " + watchDir + " to be a directory")
+	}
+	if err := os.Chdir(watchDir); err != nil {
+		log.Fatalf("cannot chdir to %s: %s", watchDir, err)
+	}
 	envconfig.Process("", &to)
+
 	reqTimeout := time.Second * time.Duration(60)
 
+	log.Println("Starting TrafficOps session")
 	toSession, err := newSession(reqTimeout, to.URL, to.User, to.Password)
+	if err != nil {
+		log.Fatalln("error starting TrafficOps session: " + err.Error())
+	}
+	fmt.Println("TrafficOps session established")
+
+	// watch for file creation in directories
+	dw, err := newDirWatcher(&toSession)
+	if err != nil {
+		log.Fatalf("%v", err)
+	}
+	defer dw.Close()
+
+	dw.watch("types", enrollType)
+	dw.watch("cdns", enrollCDN)
+	dw.watch("cachegroups", enrollCachegroup)
+	dw.watch("profiles", enrollProfile)
+	dw.watch("parameters", enrollParameter)
+	dw.watch("servers", enrollServer)
+	dw.watch("asns", enrollASN)
+	dw.watch("deliveryservices", enrollDeliveryService)
+	dw.watch("deliveryservice_servers", enrollDeliveryServiceServer)
+	dw.watch("divisions", enrollDivision)
+	dw.watch("origins", enrollOrigin)
+	dw.watch("phys_locations", enrollPhysLocation)
+	dw.watch("regions", enrollRegion)
+	dw.watch("statuses", enrollStatus)
+	dw.watch("tenants", enrollTenant)
+	dw.watch("users", enrollUser)
+
+	// create this file to indicate the enroller is ready
+	f, err := os.Create(startedFile)
 	if err != nil {
 		panic(err)
 	}
-	fmt.Println("TO session established")
-	http.HandleFunc("/", toSession.enrollerHandler())
+	f.Close()
 
-	log.Fatal(http.ListenAndServeTLS(":443", "./server.crt", "./server.key", nil))
+	var waitforever chan struct{}
+	<-waitforever
 }
diff --git a/infrastructure/cdn-in-a-box/enroller/run.sh b/infrastructure/cdn-in-a-box/enroller/run.sh
index 4cb96a9d8..0f35d1123 100755
--- a/infrastructure/cdn-in-a-box/enroller/run.sh
+++ b/infrastructure/cdn-in-a-box/enroller/run.sh
@@ -1,4 +1,5 @@
-#!/bin/bash
+#!/usr/bin/env bash
+#
 # 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
@@ -18,18 +19,38 @@
 ############################################################
 
 . ../traffic_ops/to-access.sh
-export TO_URL=https://$TO_HOST:$TO_PORT
+
+export TO_URL=https://$TO_FQDN:$TO_PORT
 export TO_USER=$TO_ADMIN_USER
 export TO_PASSWORD=$TO_ADMIN_PASSWORD
 
-key=./server.key
-cert=./server.crt
-openssl req -newkey rsa:2048 -nodes -keyout $key -x509 -days 365 -out $cert -subj "/C=$CERT_COUNTRY/ST=$CERT_STATE/L=$CERT_CITY/O=$CERT_COMPANY"
+# Wait on SSL certificate generation
+until [ -f "$X509_CA_DONE_FILE" ] 
+do
+     echo "Waiting on Shared SSL certificate generation"
+     sleep 3
+done
+
+# Source the CIAB-CA shared SSL environment
+source "$X509_CA_ENV_FILE"
+ 
+# Copy the CIAB-CA certificate to the traffic_router conf so it can be added to the trust store
+cp "$X509_CA_CERT_FILE" /usr/local/share/ca-certificates
+update-ca-certificates
 
 # Traffic Ops must be accepting connections before enroller can start
-until to-ping; do
+until nc -z $TO_FQDN $TO_PORT </dev/null >/dev/null && to-ping; do
     echo "Waiting for $TO_URL"
     sleep 5
 done
 
-go run enroller.go || tail -f /dev/null
+mkdir -p "$ENROLLER_DIR"
+if [[ ! -d $ENROLLER_DIR ]]; then
+     echo "enroller dir ${ENROLLER_DIR} not found or not a directory"
+     exit 1
+fi
+
+# clear out the enroller dir first so no files left from previous run
+rm -rf ${ENROLLER_DIR}/*
+
+./enroller "$ENROLLER_DIR" || tail -f /dev/null
diff --git a/infrastructure/cdn-in-a-box/enroller/server_template.json b/infrastructure/cdn-in-a-box/enroller/server_template.json
new file mode 100644
index 000000000..6f072d6c1
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/enroller/server_template.json
@@ -0,0 +1,20 @@
+{
+  "hostName": "$MY_HOSTNAME",
+  "domainName": "$MY_DOMAINNAME",
+  "cachegroup": "$MY_CACHE_GROUP",
+  "interfaceName": "eth0",
+  "ipAddress": "$MY_IP",
+  "ipNetmask": "$MY_NETMASK",
+  "ipGateway": "$MY_GATEWAY",
+  "ip6Address": "$MY_IP6_ADDRESS",
+  "ip6Gateway": "$MY_IP6_GATEWAY",
+  "interfaceMtu": 1500,
+  "type": "$MY_TYPE",
+  "physLocation": "Apachecon North America 2018",
+  "profile": "$MY_PROFILE",
+  "cdnName": "$MY_CDN",
+  "updPending": false,
+  "status": "$MY_STATUS",
+  "tcpPort": 80,
+  "httpsPort": 443
+}
diff --git a/infrastructure/cdn-in-a-box/mid/Dockerfile b/infrastructure/cdn-in-a-box/mid/Dockerfile
index a1dab8125..2adfd5cd2 100644
--- a/infrastructure/cdn-in-a-box/mid/Dockerfile
+++ b/infrastructure/cdn-in-a-box/mid/Dockerfile
@@ -21,18 +21,8 @@
 # Based on CentOS 7.2
 ############################################################
 
-FROM centos:7
+FROM tccache:latest
 
-ARG RPM=https://ci.trafficserver.apache.org/RPMS/CentOS7/trafficserver-7.1.2-4.el7.centos.x86_64.rpm
-ADD $RPM /trafficserver.rpm
+ADD mid/run.sh traffic_ops/to-access.sh enroller/server_template.json /
 
-RUN yum install -y epel-release
-RUN yum install -y /trafficserver.rpm iproute jq net-tools nmap-ncat && yum clean all
-
-RUN mkdir -p /var/trafficserver/cache /opt/ort && chown -R ats:ats /etc/trafficserver /var/trafficserver /opt/ort
-
-ADD mid/parameters.json mid/profile.json mid/server.json mid/setup.sh traffic_ops/to-access.sh /
-
-EXPOSE 80
-
-CMD /setup.sh
+CMD /run.sh
diff --git a/infrastructure/cdn-in-a-box/mid/run.sh b/infrastructure/cdn-in-a-box/mid/run.sh
new file mode 100755
index 000000000..27cf2b6ba
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/mid/run.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+
+# 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.
+
+set -e
+set -x
+set -m
+
+source /to-access.sh
+
+# Wait on SSL certificate generation
+until [ -f "$X509_CA_DONE_FILE" ]
+do
+  echo "Waiting on Shared SSL certificate generation"
+  sleep 3
+done
+
+# Source the CIAB-CA shared SSL environment
+source $X509_CA_ENV_FILE
+
+# Trust the CIAB-CA at the System level
+cp $X509_CA_CERT_FILE /etc/pki/ca-trust/source/anchors
+update-ca-trust extract
+
+while ! to-ping 2>/dev/null; do
+	echo "waiting for Traffic Ops"
+	sleep 5
+done
+
+CDN=CDN-in-a-Box
+
+export TO_USER=$TO_ADMIN_USER
+export TO_PASSWORD=$TO_ADMIN_PASSWORD
+
+# wait until the CDN has been registered
+found=
+while [[ -z $found ]]; do
+    echo 'waiting for enroller setup'
+    sleep 3
+    found=$(to-get api/1.3/cdns?name="$CDN" | jq -r '.response[].name')
+done
+
+to-enroll mid $CDN || (while true; do echo "enroll failed."; sleep 3 ; done)
+
+function testenrolled() {
+	local tmp="$(to-get	'api/1.3/servers?name=mid')"
+	tmp=$(echo $tmp | jq '.response[]|select(.hostName=="mid")')
+	echo "$tmp"
+}
+
+while [[ -z "$(testenrolled)" ]]; do
+	echo "waiting on enrollment"
+	sleep 3
+done
+
+# Leaves the container hanging open in the event of a failure for debugging purposes
+/opt/ort/traffic_ops_ort.py BADASS ALL "https://$TO_FQDN:$TO_PORT" "$TO_ADMIN_USER:$TO_ADMIN_PASSWORD" || { echo "Failed"; }
+
+envsubst < "/etc/cron.d/traffic_ops_ort-cron-template" > "/etc/cron.d/traffic_ops_ort-cron" && rm -f "/etc/cron.d/traffic_ops_ort-cron-template"
+chmod "0644" "/etc/cron.d/traffic_ops_ort-cron" && crontab "/etc/cron.d/traffic_ops_ort-cron"
+
+crond -im off
+
+touch /var/log/trafficserver/diags.log
+tail -F /var/log/trafficserver/diags.log
diff --git a/infrastructure/cdn-in-a-box/mid/server.json b/infrastructure/cdn-in-a-box/mid/server.json
deleted file mode 100644
index 29ab0b68a..000000000
--- a/infrastructure/cdn-in-a-box/mid/server.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-	"hostName": "MY_HOSTNAME",
-	"domainName": "MY_DOMAINNAME",
-	"cachegroupId": CACHE_GROUP_ID,
-	"interfaceName": "MY_IFACE_NAME",
-	"ipAddress": "MY_IP",
-	"ipNetmask": "MY_NETMASK",
-	"ipGateway": "MY_GATEWAY",
-	"interfaceMtu": MY_MTU,
-	"physLocationId": MY_LOCATION,
-	"typeId": MY_TYPE,
-	"profileId": MY_PROFILE_ID,
-	"cdnId": MY_CDN_ID,
-	"updPending": false,
-	"statusId": REPORTED_ID,
-	"tcpPort": 80
-}
diff --git a/infrastructure/cdn-in-a-box/mid/setup.sh b/infrastructure/cdn-in-a-box/mid/setup.sh
deleted file mode 100755
index 0ad840184..000000000
--- a/infrastructure/cdn-in-a-box/mid/setup.sh
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/bash
-
-# 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.
-
-set -e
-set -x
-set -m
-
-traffic_server start &
-
-INFO=$(ifconfig eth0 | grep 'inet ' | tr -s ' ')
-myIP=$(echo $INFO | cut -d' ' -f2)
-device=eth0
-gateway=$(route -n | grep $device | grep -E '^0\.0\.0\.0' | tr -s ' ' | cut -d ' ' -f2)
-mtu=$(ip addr show | grep $device | head -n 1 | cut -d ' ' -f5)
-netmask=$(echo $INFO | cut -d ' ' -f4)
-
-sed -ie "s;MY_HOSTNAME;$(hostname -s);g" /server.json
-sed -ie "s;MY_DOMAINNAME;$(dnsdomainname);g" /server.json
-sed -ie "s;MY_IFACE_NAME;$device;g" /server.json
-sed -ie "s;MY_MTU;$mtu;g" /server.json
-sed -ie "s;MY_GATEWAY;$gateway;g" /server.json
-sed -ie "s;MY_NETMASK;$netmask;g" /server.json
-sed -ie "s;MY_IP;$myIP;g" /server.json
-
-source /to-access.sh
-
-while ! to-ping 2>/dev/null; do
-	echo "waiting for Traffic Ops"
-	sleep 3
-done
-
-# Gets our CDN ID
-CDN=$(to-get api/1.3/cdns | jq '.response|.[]|select(.name=="CDN-in-a-Box")|.id')
-while [[ -z "$CDN" ]]; do
-	echo "waiting for trafficops setup to complete..."
-	sleep 3
-	CDN=$(to-get api/1.3/cdns | jq '.response|.[]|select(.name=="CDN-in-a-Box")|.id')
-done
-
-
-# Now we upload a profile for later use
-sed -ie "s;CDN_ID;$CDN;g" /profile.json
-PROFILE=$(to-post api/1.3/profiles /profile.json | jq '.response')
-PROFILENAME=$(echo $PROFILE | jq '.name' | tr -d '"')
-PROFILEID=$(echo $PROFILE | jq '.id')
-to-post api/1.3/profiles/name/$PROFILENAME/parameters /parameters.json
-echo
-
-# Gets the location ID
-location=$(to-get api/1.3/phys_locations | jq '.response|.[]|select(.name=="CDN_in_a_Box")|.id')
-while [[ -z "$location" ]]; do
-	echo "Waiting for location setup"
-	sleep 3
-	location=$(to-get api/1.3/phys_locations | jq '.response|.[]|select(.name=="CDN_in_a_Box")|.id')
-done
-
-# Gets the id of a MID server type
-TYPE=$(to-get api/1.3/types | jq '.response|.[]|select(.name=="MID")|.id')
-
-# Gets the id of the 'REPORTED' status
-REPORTED=$(to-get api/1.3/statuses | jq '.response|.[]|select(.name=="REPORTED")|.id')
-
-# Gets the cachegroup ID
-CACHEGROUP=$(to-get api/1.3/cachegroups | jq '.response|.[]|select(.name=="CDN_in_a_Box_Mid")|.id')
-while [[ -z CACHEGROUP ]]; do
-	echo "waiting for trafficops setup to complete..."
-	sleep 3
-	CACHEGROUP=$(to-get api/1.3/cachegroups | jq '.response|.[]|select(.name=="CDN_in_a_Box_Mid")|.id')
-done
-
-# Now put it all together and send it up
-sed -ie "s;MY_LOCATION;$location;g" /server.json
-sed -ie "s;MY_TYPE;$TYPE;g" /server.json
-sed -ie "s;MY_CDN_ID;$CDN;g" /server.json
-sed -ie "s;REPORTED_ID;$REPORTED;g" /server.json
-sed -ie "s;CACHE_GROUP_ID;$CACHEGROUP;g" /server.json
-sed -ie "s;MY_PROFILE_ID;$PROFILEID;g" /server.json
-cat /server.json
-to-post api/1.3/servers /server.json
-echo
-
-fg
diff --git a/infrastructure/cdn-in-a-box/origin/Dockerfile b/infrastructure/cdn-in-a-box/origin/Dockerfile
index 2bfcfacea..822544346 100644
--- a/infrastructure/cdn-in-a-box/origin/Dockerfile
+++ b/infrastructure/cdn-in-a-box/origin/Dockerfile
@@ -23,16 +23,17 @@
 
 FROM alpine:latest
 
-RUN apk add --no-cache lighttpd
+RUN apk add --no-cache lighttpd bash curl
 
 RUN rm /etc/lighttpd/lighttpd.conf
 RUN rm -rf /var/www/localhost/
 RUN mkdir -p /var/www/html/
 
-ADD lighttpd.conf /etc/lighttpd/lighttpd.conf
-ADD index.html /var/www/html/index.html
-ADD tc_logo.svg /var/www/html/tc_logo.svg
+ADD origin/lighttpd.conf /etc/lighttpd/lighttpd.conf
+ADD origin/index.html /var/www/html/index.html
+ADD origin/tc_logo.svg /var/www/html/tc_logo.svg
+ADD origin/run.sh traffic_ops/to-access.sh /
 
 EXPOSE 80
 
-CMD lighttpd -t -f /etc/lighttpd/lighttpd.conf && lighttpd -D -f /etc/lighttpd/lighttpd.conf
+CMD /run.sh
diff --git a/infrastructure/cdn-in-a-box/origin/run.sh b/infrastructure/cdn-in-a-box/origin/run.sh
new file mode 100755
index 000000000..a6f4d4066
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/origin/run.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+#
+# 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.
+
+set -e
+set -x
+set -m
+
+source /to-access.sh
+
+# Wait on SSL certificate generation
+until [ -f "$X509_CA_DONE_FILE" ] 
+do
+     echo "Waiting on Shared SSL certificate generation"
+     sleep 3
+done
+
+# Source the CIAB-CA shared SSL environment
+source "$X509_CA_ENV_FILE"
+
+# Copy the CIAB-CA certificate to the traffic_router conf so it can be added to the trust store
+cp $X509_CA_CERT_FILE /usr/local/share/ca-certificates
+update-ca-certificates
+
+while ! to-ping 2>/dev/null; do
+  echo "waiting for Traffic Ops"
+  sleep 3
+done
+
+export TO_USER=$TO_ADMIN_USER
+export TO_PASSWORD=$TO_ADMIN_PASSWORD
+
+to-enroll origin || (while true; do echo "enroll failed."; sleep 3 ; done)
+
+lighttpd -t -f /etc/lighttpd/lighttpd.conf && lighttpd -D -f /etc/lighttpd/lighttpd.conf
diff --git a/infrastructure/cdn-in-a-box/traffic_monitor/Dockerfile b/infrastructure/cdn-in-a-box/traffic_monitor/Dockerfile
index 0f3aa5e0c..405dd782c 100644
--- a/infrastructure/cdn-in-a-box/traffic_monitor/Dockerfile
+++ b/infrastructure/cdn-in-a-box/traffic_monitor/Dockerfile
@@ -25,24 +25,26 @@ FROM centos/systemd
 ARG RPM=traffic_monitor/traffic_monitor.rpm
 ADD $RPM /
 
-RUN yum install -y \
-        epel-release \
-        initscripts && \
+RUN yum install -y epel-release && \
     yum install -y \
         jq \
         nmap-ncat \
         iproute \
         net-tools \
-        /$(basename $RPM) && \
+        gettext \
+        bind-utils \
+        openssl \
+        initscripts && \
+    yum install -y  /$(basename $RPM) && \
     rm /$(basename $RPM) && \
     yum clean all
 
 RUN mkdir -p /opt/traffic_monitor/conf
 ADD traffic_monitor/traffic_monitor.cfg /opt/traffic_monitor/conf/
 
-ADD traffic_monitor/parameters.json \
+ADD enroller/server_template.json \
+    traffic_monitor/parameters.json \
     traffic_monitor/profile.json \
-    traffic_monitor/server.json \
     traffic_ops/to-access.sh \
     /
 
diff --git a/infrastructure/cdn-in-a-box/traffic_monitor/run.sh b/infrastructure/cdn-in-a-box/traffic_monitor/run.sh
index 5160f94b6..5029eec95 100755
--- a/infrastructure/cdn-in-a-box/traffic_monitor/run.sh
+++ b/infrastructure/cdn-in-a-box/traffic_monitor/run.sh
@@ -32,46 +32,57 @@ set -e
 set -x
 set -m
 
-envvars=( TO_HOST TO_PORT TM_USER TM_PASSWORD)
+envvars=( TO_HOST TO_PORT TM_PORT TM_USER TM_PASSWORD)
 for v in $envvars
 do
 	if [[ -z $$v ]]; then echo "$v is unset"; exit 1; fi
 done
 
-IP=$(ip addr | grep 'global' | grep -v "inet6" | head -n1 | awk '{print $2}' | cut -f1 -d'/')
-NETMASK=$(ifconfig eth0 | grep 'inet ' | tr -s ' ' | cut -d ' ' -f4)
-GATEWAY="$(ip route | grep default | awk '{print $3}')"
-TO_URL="https://$TO_HOST:$TO_PORT"
+source /to-access.sh
+
+# Wait on SSL certificate generation
+until [ -f "$X509_CA_DONE_FILE" ] 
+do
+  echo "Waiting on Shared SSL certificate generation"
+  sleep 3
+done
+
+# Source the CIAB-CA shared SSL environment
+source $X509_CA_ENV_FILE
+
+# Trust the CIAB-CA at the System level
+cp $X509_CA_CERT_FILE /etc/pki/ca-trust/source/anchors
+update-ca-trust extract
+
+# Enroll with traffic ops
+CDN=CDN-in-a-Box
+TO_URL="https://$TO_FQDN:$TO_PORT"
+to-enroll tm $CDN || (while true; do echo "enroll failed."; sleep 3 ; done)
+
+# Configure Traffic Monitor
 cat > /opt/traffic_monitor/conf/traffic_ops.cfg <<- ENDOFMESSAGE
 {
 	"username": "$TM_USER",
 	"password": "$TM_PASSWORD",
 	"url": "$TO_URL",
 	"insecure": true,
-	"cdnName": "CDN-in-a-Box",
-	"httpListener": ":80"
+	"cdnName": "$CDN",
+	"httpListener": ":$TM_PORT"
 }
 ENDOFMESSAGE
 
-sed -ie "s;MY_HOSTNAME;$(hostname -s);g" /server.json
-sed -ie "s;MY_DOMAINNAME;$(dnsdomainname);g" /server.json
-sed -ie "s;MY_GATEWAY;$GATEWAY;g" /server.json
-sed -ie "s;MY_NETMASK;$NETMASK;g" /server.json
-sed -ie "s;MY_IP;$IP;g" /server.json
-
-source /to-access.sh
-
 while ! to-ping 2>/dev/null; do
-	echo "waiting for traffic_ops..."
+	echo "waiting for trafficops ($TO_URL)..."
 	sleep 3
 done
 
+
 export TO_USER=$TO_ADMIN_USER
 export TO_PASSWORD=$TO_ADMIN_PASSWORD
 
 # There's a race condition with setting the TM credentials and TO actually creating
 # the TM user
-until to-get api/1.3/users?username="$TM_USER" | jq -c -e '.response[].username|length'; do
+until to-get "api/1.3/users?username=$TM_USER" 2>/dev/null | jq -c -e '.response[].username|length'; do
 	echo "waiting for TM_USER creation..."
 	sleep 3
 done
@@ -80,63 +91,17 @@ done
 export TO_USER="$TM_USER"
 export TO_PASSWORD="$TM_PASSWORD"
 
-# Gets our CDN ID
-CDN=$(to-get api/1.3/cdns | jq '.response|.[]|select(.name=="CDN-in-a-Box")|.id')
-while [[ -z "$CDN" ]]; do
-	echo "waiting for traffic_ops setup to complete..."
-	sleep 3
-	CDN=$(to-get api/1.3/cdns | jq '.response|.[]|select(.name=="CDN-in-a-Box")|.id')
-done
-
-# Now we upload a profile for later use
-sed -ie "s;CDN_ID;$CDN;g" /profile.json
-cat /profile.json
-PROFILE=$(to-post api/1.3/profiles /profile.json | jq '.response')
-PROFILENAME=$(echo $PROFILE | jq '.name' | tr -d '"')
-PROFILEID=$(echo $PROFILE | jq '.id')
-to-post api/1.3/profiles/name/$PROFILENAME/parameters /parameters.json
-echo
-
-# Gets the location ID
-location=$(to-get api/1.3/phys_locations | jq '.response|.[]|select(.name=="CDN_in_a_Box")|.id')
-while [[ -z "$location" ]]; do
-	echo "Waiting for location setup"
-	sleep 3
-	location=$(to-get api/1.3/phys_locations | jq '.response|.[]|select(.name=="CDN_in_a_Box")|.id')
-done
-
-# Gets the id of a RASCAL server type
-TYPE=$(to-get api/1.3/types | jq '.response|.[]|select(.name=="RASCAL")|.id')
-
-# Gets the id of the 'ONLINE' status
-ONLINE=$(to-get api/1.3/statuses | jq '.response|.[]|select(.name=="ONLINE")|.id')
-
-# Gets the cachegroup ID
-CACHEGROUP=$(to-get api/1.3/cachegroups | jq '.response|.[]|select(.name=="CDN_in_a_Box_Mid")|.id')
-while [[ -z "$CACHEGROUP" ]]; do
-	echo "waiting for trafficops setup to complete..."
-	sleep 3
-	CACHEGROUP=$(to-get api/1.3/cachegroups | jq '.response|.[]|select(.name=="CDN_in_a_Box_Mid")|.id')
-done
-
-# Now put it all together and send it up
-sed -ie "s;MY_LOCATION;$location;g" /server.json
-sed -ie "s;MY_TYPE;$TYPE;g" /server.json
-sed -ie "s;MY_CDN_ID;$CDN;g" /server.json
-sed -ie "s;MY_STATUS;$ONLINE;g" /server.json
-sed -ie "s;CACHE_GROUP_ID;$CACHEGROUP;g" /server.json
-sed -ie "s;MY_PROFILE_ID;$PROFILEID;g" /server.json
-cat /server.json
-to-post api/1.3/servers /server.json
-echo
-
 export TO_USER=$TO_ADMIN_USER
 export TO_PASSWORD=$TO_ADMIN_PASSWORD
-. /to-access.sh
-to-enroll $(hostname -s)
 
 touch /opt/traffic_monitor/var/log/traffic_monitor.log
 
+# Do not start until there is a valid CRConfig available
+until [ $(to-get '/CRConfig-Snapshots/CDN-in-a-Box/CRConfig.json' 2>/dev/null | jq -c -e '.config|length') -gt 0 ] ; do 
+	echo "Waiting on valid CRConfig..."; 
+  	sleep 3; 
+done
+
 cd /opt/traffic_monitor
 /opt/traffic_monitor/bin/traffic_monitor -opsCfg /opt/traffic_monitor/conf/traffic_ops.cfg -config /opt/traffic_monitor/conf/traffic_monitor.cfg &
 disown
diff --git a/infrastructure/cdn-in-a-box/traffic_monitor/server.json b/infrastructure/cdn-in-a-box/traffic_monitor/server.json
deleted file mode 100644
index ae78ca215..000000000
--- a/infrastructure/cdn-in-a-box/traffic_monitor/server.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-	"hostName": "MY_HOSTNAME",
-	"domainName": "MY_DOMAINNAME",
-	"cachegroupId": CACHE_GROUP_ID,
-	"interfaceName": "eth0",
-	"ipAddress": "MY_IP",
-	"ipNetmask": "MY_NETMASK",
-	"ipGateway": "MY_GATEWAY",
-	"interfaceMtu": 9000,
-	"physLocationId": MY_LOCATION,
-	"typeId": MY_TYPE,
-	"profileId": MY_PROFILE_ID,
-	"cdnId": MY_CDN_ID,
-	"updPending": false,
-	"statusId": MY_STATUS,
-	"tcpPort": 80
-}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile b/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile
index 0b19e4326..471fcc3ef 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile
+++ b/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile
@@ -23,17 +23,21 @@
 
 FROM centos:7
 
-RUN yum install -y https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-redhat96-9.6-3.noarch.rpm
-RUN yum install -y \
-	cpanminus \
-	epel-release \
-	gettext \
-	nmap-ncat \
-	openssl \
-	perl \
-	perl-Crypt-ScryptKDF \
-	perl-Test-CPAN-Meta && \
-	yum clean all
+RUN yum install -y epel-release && \
+    yum install -y https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-redhat96-9.6-3.noarch.rpm && \
+    yum install -y \
+	    cpanminus \
+      bind-utils \ 
+      net-tools \ 
+	    gettext \
+	    nmap-ncat \
+	    openssl \
+	    perl \
+	    perl-Crypt-ScryptKDF \
+	    perl-Test-CPAN-Meta \
+      perl-JSON-PP \
+      git \ 
+      golang 
 
 RUN cpanm Carton
 RUN yum install -y perl-DBIx-Connector
@@ -43,9 +47,7 @@ ARG TRAFFIC_OPS_RPM=traffic_ops/traffic_ops.rpm
 ADD $TRAFFIC_OPS_RPM /
 RUN yum install -y \
         /$(basename $TRAFFIC_OPS_RPM) \
-        git \
-        golang && \
-    rm /$(basename $TRAFFIC_OPS_RPM) && \
+        rm /$(basename $TRAFFIC_OPS_RPM) && \
     yum clean all
 
 # copy any dir structure in overrides to TO -- allows modification of the install and shortcut to get perl modules/goose installed
@@ -53,7 +55,6 @@ COPY traffic_ops/overrides/ /opt/traffic_ops/.
 
 WORKDIR /opt/traffic_ops/app
 
-
 # run carton whether or not local dir was installed
 RUN POSTGRES_HOME=/usr/pgsql-9.6 carton && \
      rm -rf $HOME/.cpan* /tmp/Dockerfile /tmp/local.tar.gz
@@ -66,21 +67,20 @@ RUN export PERL5LIB=/opt/traffic_ops/app/local/lib/perl5/:/opt/traffic_ops/insta
 	&& /opt/traffic_ops/install/bin/download_web_deps -i || \
 	true # keep a failure here from failing all..
 
-# \todo add Drive Letters to postinstall input
-# RUN sed -i -- 's/"value": "b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y"/"value": "0"/g' /opt/traffic_ops/install/data/profiles/profile.trafficserver_edge.traffic_ops
-# RUN sed -i -- 's/"value": "b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y"/"value": "0"/g' /opt/traffic_ops/install/data/profiles/profile.trafficserver_mid.traffic_ops
-
-RUN echo "{\"user\": \"riakuser\",\"password\": \"$TRAFFIC_VAULT_PASS\"}" > /opt/traffic_ops/app/conf/production/riak.conf
+ADD http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz /
 
-EXPOSE 6443
+EXPOSE 443
 WORKDIR /opt/traffic_ops/app
 ENV MOJO_MODE production
 
-ADD traffic_ops/run.sh \
+ADD enroller/server_template.json \
+    traffic_ops/run.sh \
     traffic_ops/config.sh \
     traffic_ops/adduser.pl \
     traffic_ops/to-access.sh \
+    traffic_ops/generate-certs.sh \
     traffic_ops/trafficops-init.sh \
+    variables.env \
     /
 
 ADD traffic_ops_data /traffic_ops_data
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile-go b/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile-go
index b6bee6dc2..9bf54a614 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile-go
+++ b/infrastructure/cdn-in-a-box/traffic_ops/Dockerfile-go
@@ -28,11 +28,15 @@ RUN yum -y install jq && yum clean all
 RUN mkdir -p /opt/traffic_ops/app/bin /opt/traffic_ops/app/conf/production /opt/traffic_ops/app/db
 COPY --from=trafficops-perl /opt/traffic_ops/app/bin/traffic_ops_golang /opt/traffic_ops/app/bin/traffic_ops_golang
 
-EXPOSE 6443
+EXPOSE 443
 WORKDIR /opt/traffic_ops/app
+RUN yum install -y bind-utils net-tools gettext perl-JSON-PP
 
-ADD traffic_ops/config.sh \
+
+ADD enroller/server_template.json \
+    traffic_ops/config.sh \
     traffic_ops/run-go.sh \
     traffic_ops/to-access.sh \
     /
+
 CMD /run-go.sh
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/config.sh b/infrastructure/cdn-in-a-box/traffic_ops/config.sh
index b0ad802e1..b310d0111 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/config.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/config.sh
@@ -29,27 +29,41 @@
 # DB_NAME
 # ADMIN_USER
 # ADMIN_PASS
-# CERT_COUNTRY
-# CERT_STATE
-# CERT_CITY
-# CERT_COMPANY
-# DOMAIN
-
+# TO_HOST
+# TO_PORT
+# TO_PERL_HOST
+# TO_PERL_PORT
+# TP_HOST
+#
 # Check that env vars are set
-envvars=( DB_SERVER DB_PORT DB_ROOT_PASS DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS CERT_COUNTRY CERT_STATE CERT_CITY CERT_COMPANY DOMAIN)
+envvars=( DB_SERVER DB_PORT DB_ROOT_PASS DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS DOMAIN TO_PERL_HOST TO_PERL_PORT TO_HOST TO_PORT TP_HOST)
 for v in $envvars
 do
 	if [[ -z $$v ]]; then echo "$v is unset"; exit 1; fi
 done
 
-key=/server.key
-crt=/server.crt
+until [ -f "$X509_CA_DONE_FILE" ] ; do
+  echo "Waiting on SSL certificate generation."
+  sleep 2
+done
+
+source "$X509_CA_ENV_FILE"
+
+# Add the CA certificate to sysem TLS trust store
+cp $X509_CA_CERT_FILE /etc/pki/ca-trust/source/anchors
+update-ca-trust extract
+
+crt="$X509_INFRA_CERT_FILE"
+key="$X509_INFRA_KEY_FILE"
+
+echo "crt=$crt"
+echo "key=$key"
 
 cat <<-EOF >/opt/traffic_ops/app/conf/cdn.conf
 {
     "hypnotoad" : {
         "listen" : [
-            "https://trafficops-perl:60443?cert=$crt&key=$key&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED"
+            "https://$TO_PERL_FQDN:$TO_PERL_PORT?cert=$crt&key=$key&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED"
         ],
         "user" : "trafops",
         "group" : "trafops",
@@ -59,7 +73,7 @@ cat <<-EOF >/opt/traffic_ops/app/conf/cdn.conf
     },
     "traffic_ops_golang" : {
 	"insecure": true,
-        "port" : "6443",
+        "port" : "$TO_PORT",
         "proxy_timeout" : 60,
         "proxy_keep_alive" : 60,
         "proxy_tls_timeout" : 60,
@@ -82,18 +96,18 @@ cat <<-EOF >/opt/traffic_ops/app/conf/cdn.conf
         "access_control_allow_origin" : "*"
     },
     "to" : {
-        "base_url" : "https://$TO_HOST",
-        "email_from" : "no-reply@traffic-ops-domain.com",
+        "base_url" : "https://$TO_FQDN",
+        "email_from" : "no-reply@$DOMAIN",
         "no_account_found_msg" : "A Traffic Ops user account is required for access. Please contact your Traffic Ops user administrator."
     },
     "portal" : {
-        "base_url" : "https://$TP_HOST/!#/",
-        "email_from" : "no-reply@traffic-portal-domain.com",
+        "base_url" : "https://$TP_FQDN/!#/",
+        "email_from" : "no-reply@$DOMAIN",
         "pass_reset_path" : "user",
         "user_register_path" : "user"
     },
     "secrets" : [
-        "mONKEYDOmONKEYSEE."
+        "$TO_SECRET"
     ],
     "geniso" : {
         "iso_root_path" : "/opt/traffic_ops/app/public"
@@ -106,7 +120,7 @@ cat <<-EOF >/opt/traffic_ops/app/conf/production/database.conf
 {
         "description": "Local PostgreSQL database on port 5432",
         "dbname": "$DB_NAME",
-        "hostname": "$DB_SERVER",
+        "hostname": "$DB_FQDN",
         "user": "$DB_USER",
         "password": "$DB_USER_PASS",
         "port": "$DB_PORT",
@@ -121,11 +135,14 @@ name: dbconf.yml
 
 production:
   driver: postgres
-  open: host=$DB_SERVER port=$DB_PORT user=$DB_USER password=$DB_USER_PASS dbname=$DB_NAME sslmode=disable
+  open: host=$DB_FQDN port=$DB_PORT user=$DB_USER password=$DB_USER_PASS dbname=$DB_NAME sslmode=disable
 test:
   driver: postgres
-  open: host=$DB_SERVER port=$DB_PORT user=$DB_USER password=$DB_USER_PASS dbname=to_test sslmode=disable
+  open: host=$DB_FQDN port=$DB_PORT user=$DB_USER password=$DB_USER_PASS dbname=to_test sslmode=disable
 EOF
 
-openssl req -newkey rsa:2048 -nodes -keyout $key -x509 -days 365 -out $crt -subj "/C=$CERT_COUNTRY/ST=$CERT_STATE/L=$CERT_CITY/O=$CERT_COMPANY"
-chown trafops:trafops $key $crt
+cat <<-EOF >/opt/traffic_ops/app/conf/production/riak.conf
+{     "user": "$TV_RIAK_USER",
+  "password": "$TV_RIAK_PASSWORD"
+}
+EOF
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/generate-certs.sh b/infrastructure/cdn-in-a-box/traffic_ops/generate-certs.sh
new file mode 100755
index 000000000..c834d9b50
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops/generate-certs.sh
@@ -0,0 +1,274 @@
+#!/bin/bash
+
+X509_CA_DEFAULT_NAME="ca"
+X509_CA_DEFAULT_COUNTRY="ZZ"
+X509_CA_DEFAULT_STATE="SomeState"
+X509_CA_DEFAULT_CITY="SomeCity"
+X509_CA_DEFAULT_COMPANY="SomeCompany"
+X509_CA_DEFAULT_ORG="SomeOrganization"
+X509_CA_DEFAULT_ORGUNIT="SomeOrgUnit"
+X509_CA_DEFAULT_EMAIL="no-reply@some.host.test"
+X509_CA_DEFAULT_DIGEST="sha256"
+X509_CA_DEFAULT_DURATION_DAYS="365"
+X509_CA_DEFAULT_KEYTYPE="rsa"
+X509_CA_DEFAULT_KEYSIZE=4096 
+X509_CA_DEFAULT_UMASK=0002
+
+x509v3_usage()
+{
+	local prog="$1"
+
+	echo "Usage: $prog [output-dir]"
+}
+
+x509v3_init()
+{
+	local output_dir="$1"
+
+	X509_CA_DIR="${X509_CA_DIR:-$output_dir}"	
+  X509_CA_NAME="${X509_CA_NAME:-$X509_CA_DEFAULT_NAME}"
+  X509_CA_COUNTRY="${X509_CA_COUNTRY:-$X509_CA_DEFAULT_COUNTRY}"
+  X509_CA_STATE="${X509_CA_STATE:-$X509_CA_DEFAULT_STATE}"
+  X509_CA_CITY="${X509_CA_CITY:-$X509_CA_DEFAULT_CITY}"
+  X509_CA_COMPANY="${X509_CA_COMPANY:-$X509_CA_DEFAULT_COMPANY}"
+  X509_CA_ORG="${X509_CA_ORG:-$X509_CA_DEFAULT_ORG}"
+  X509_CA_ORGUNIT="${X509_CA_ORGUNIT:-$X509_CA_DEFAULT_ORGUNIT}"
+  X509_CA_EMAIL="${X509_CA_EMAIL:--$X509_CA_DEFAULT_EMAIL}"
+  X509_CA_DIGEST="${X509_CA_DIGEST:-$X509_CA_DEFAULT_DIGEST}"
+  X509_CA_DURATION_DAYS="${X509_CA_DURATION_DAYS:-$X509_CA_DEFAULT_DURATION_DAYS}"
+  X509_CA_KEYTYPE="${X509_CA_KEYTYPE:-$X509_CA_DEFAULT_KEYTYPE}"
+  X509_CA_KEYSIZE="${X509_CA_KEYSIZE:-$X509_CA_DEFAULT_KEYSIZE}"
+  X509_CA_UMASK="${X509_CA_UMASK:-$X509_CA_DEFAULT_UMASK}"
+
+  # Runtime
+	X509_CA_CERT_FILE="$X509_CA_DIR/$X509_CA_NAME.crt"
+	X509_CA_KEY_FILE="$X509_CA_DIR/$X509_CA_NAME.key"
+	X509_CA_CONFIG_FILE="$X509_CA_DIR/$X509_CA_NAME.config"
+	X509_CA_DB_FILE="$X509_CA_DIR/$X509_CA_NAME.db"
+	X509_CA_REVOKE_FILE="$X509_CA_DIR/$X509_CA_NAME.crl"
+	X509_CA_SERIAL_FILE="$X509_CA_DIR/$X509_CA_NAME.serial"
+	X509_CA_ENV_FILE="$X509_CA_DIR/environment"
+	X509_CA_DONE_FILE="$X509_CA_DIR/completed"
+  
+  # Set the Umask
+  umask $X509_CA_DEFAULT_UMASK
+	
+  # If no X509_CA directory exists, create it
+  if [ ! -d "$X509_CA_DIR" ] ; then
+		mkdir -p "$X509_CA_DIR"
+		x509v3_create_ca
+  else 
+		echo "Previous X509v3 CA Directory Exists..., skipping..."
+  fi
+
+}
+
+x509v3_create_ca()
+{
+	# Use today's epoch date for the first serial number
+	echo "$(date +%s)" > "$X509_CA_SERIAL_FILE"
+
+	# Create the CA index file
+ 	touch "$X509_CA_DB_FILE"
+
+	# Create the CA environment file
+	touch "$X509_CA_ENV_FILE"
+
+	local cn="$X509_CA_DEFAULT_ORG Root CA (CA)"
+
+	local ca_config=""`
+		`"[ca]\n"`
+    `"default_ca = $X509_CA_DEFAULT_NAME\n\n"`
+		`"[$X509_CA_DEFAULT_NAME]\n"`
+		`"new_certs_dir = $X509_CA_DIR\n"`
+		`"certificate = $X509_CA_CERT_FILE\n"`
+		`"private_key = $X509_CA_KEY_FILE\n"`
+		`"serial = $X509_CA_SERIAL_FILE\n"`
+		`"database = $X509_CA_DB_FILE\n"`
+		`"default_md = $X509_CA_DEFAULT_DIGEST\n"`
+		`"default_days = $X509_CA_DEFAULT_DURATION_DAYS\n"`
+		`"prompt = no\n"`
+		`"preserve = no\n\n"`
+    `"[policy_match]\n"`
+    `"countryName = match\n"`
+    `"stateOrProvinceName = match\n"`
+    `"organizationName = match\n"`
+    `"organizationalUnitName = optional\n"`
+    `"commonName = supplied\n"`
+    `"emailAddress = optional\n\n"`
+    `"[policy_anything]\n"`
+    `"countryName = optional\n"`
+    `"stateOrProvinceName = optional\n"`
+    `"localityName = optional\n"`
+    `"organizationName = optional\n"`
+    `"organizationalUnitName = optional\n"`
+    `"commonName = supplied\n"`
+		`"emailAddress = optional\n\n"`
+    `"[req]\n"`
+    `"default_bits = $X509_CA_DEFAULT_KEYSIZE\n"`
+    `"distinguished_name = req_dn\n"`
+    `"string_mask = nombstr\n"`
+    `"x509_extensions = v3_ca\n"`
+    `"[req_dn]\n"`
+    `"countryName = Country Name (2 letter code)\n"`
+    `"countryName_default = $X509_CA_DEFAULT_COUNTRY\n"`
+    `"countryName_min = 2\n"`
+    `"countryName_max = 2\n"`
+    `"stateOrProvinceName = State or Province Name (full name)\n"`
+    `"stateOrProvinceName_default = $X509_CA_DEFAULT_STATE\n"`
+    `"localityName = Locality Name (eg, city)\n"`
+    `"localityName_default = $X509_CA_DEFAULT_CITY\n"`
+    `"0.organizationName = Organization Name (eg, company)\n"`
+    `"0.organizationName_default = $X509_CA_DEFAULT_ORG\n"`
+    `"organizationalUnitName = Organizational Unit Name (eg, section)\n"`
+    `"organizationalUnitName_default = $X509_CA_DEFAULT_ORGUNIT\n"`
+    `"commonName = Common Name (eg, YOUR name)\n"`
+    `"commonName_max = 64\n"`
+    `"emailAddress = Email Address\n"`
+    `"emailAddress_max = 64\n"`
+    `"emailAddress_default = $X509_CA_DEFAULT_EMAIL\n\n"`
+		`"[v3_ca]\n"`
+		`"basicConstraints = CA:TRUE\n"`
+		`"subjectKeyIdentifier = hash\n"`
+		`"keyUsage = cRLSign, keyCertSign\n"`
+		`"extendedKeyUsage = serverAuth, clientAuth\n\n"
+
+		##"req_extensions = v3_req\n"
+		#"[v3_req]\n"
+		#"basicConstraints = CA:FALSE\n"
+    #"keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n"
+		#"extendedKeyUsage = serverAuth, clientAuth, codeSigning, emailProtection\n"
+		#"subjectKeyIdentifier = hash\n"
+		#"authorityKeyIdentifier = keyid,issuer\n
+
+		echo "Writing CA openssl CA Config File"
+	  echo -e "$ca_config" > "$X509_CA_CONFIG_FILE"
+
+		# Create new CA Certificate and Key
+ 		openssl req -x509 -nodes -extensions v3_ca \
+			-config "$X509_CA_CONFIG_FILE" \
+      -newkey "$X509_CA_DEFAULT_KEYTYPE:$X509_CA_DEFAULT_KEYSIZE" \
+			-keyout "$X509_CA_KEY_FILE" \
+      -out "$X509_CA_CERT_FILE" \
+  		-subj "/C=$X509_CA_DEFAULT_COUNTRY/ST=$X509_CA_DEFAULT_STATE/L=$X509_CA_DEFAULT_CITY/O=$X509_CA_DEFAULT_ORG/OU=$X509_CA_DEFAULT_ORG/CN=$cn/emailAddress=$X509_CA_DEFAULT_EMAIL/" 
+		retcode=$?
+
+		echo "CreateX509 Cert RetCode=$retcode"
+	
+		return $retcode
+}
+
+x509v3_gen_alt_names()
+{
+	local names="$1"
+
+	[ -z "$name" ] && return 1
+
+	local alt_names_text=""`
+		`"[alt_names]\n"
+
+	local count=1
+	for n in $names 
+	do 
+		alt_names_text="${alt_names_text}DNS.$count = $n"
+		((count+=1))
+	done
+
+	echo "$alt_names_text"
+
+	return 0
+}
+
+x509v3_create_cert()
+{
+  local name="$1"
+  local cn="$2"
+	local alt_names="$3"
+
+	echo "name=[$name]"
+	echo "cn=[$cn]"
+	echo "alt_names=[$alt_names]"
+	
+	# TODO: Error Checking
+	
+	local config_file="$X509_CA_DIR/$cn.config"
+	local exten_file="$X509_CA_DIR/$cn.exten"
+	local cert_file="$X509_CA_DIR/$cn.crt"
+	local key_file="$X509_CA_DIR/$cn.key"
+	local request_file="$X509_CA_DIR/$cn.csr"
+	local env_name=$(echo -e "$name" | tr '[a-z]' '[A-Z]' | sed 's/\./_/g')
+
+	# CN is always included in SAN list as it is required by all modern web browsers.
+	cn="*.${cn}"		
+  alt_names=$(x509v3_gen_alt_names "$cn $san_list") 	
+
+	local request_config=""`
+		`"[req]\n"`
+		`"encrypt_key = no\n"`
+		`"prompt = no\n"`
+		`"utf8 = yes\n"`
+		`"default_md = $X509_CA_DEFAULT_DIGEST\n"`
+		`"default_bits = $X509_CERT_KEYSIZE\n"`
+		`"distinguished_name = dn\n"`
+		`"req_extensions = req_ext\n\n"`
+		`"[dn]\n"`
+		`"C = $X509_CA_DEFAULT_COUNTRY\n"`
+		`"ST = $X509_CA_DEFAULT_STATE\n"`
+		`"L = $X509_CA_DEFAULT_CITY\n"`
+		`"O  = $X509_CA_DEFAULT_ORG\n"`
+		`"CN = $cn\n\n"`
+		`"[req_ext]\n"`
+		`"basicConstraints=CA:FALSE\n"`
+		`"subjectKeyIdentifier = hash\n"`
+		`"subjectAltName=@alt_names\n\n"
+
+	local exten_config=""`
+		`"[v3_ext]\n"`
+		`"basicConstraints=CA:FALSE\n"`
+		`"subjectKeyIdentifier = hash\n"`
+		`"subjectAltName=@alt_names\n\n"
+
+  echo "Creating x509v3 request for cn=$cn type $type..."
+
+	# Create OpenSSL config file this request
+	echo -e "${request_config}${alt_names}" > "$config_file"
+	echo -e "${exten_config}${alt_names}" > "$exten_file"
+
+	# Create the x509 request config file 
+  openssl req -new -nodes \
+	 -config "$config_file" \
+   -newkey "$X509_CA_DEFAULT_KEYTYPE:$X509_CA_DEFAULT_KEYSIZE" \
+	 -keyout "$key_file" \
+   -out "$request_file" 
+
+  retcode=$?
+	echo "Create X509 Req RetCode=$retcode"
+
+  # Sign with the CA
+  openssl ca -batch \
+   -policy policy_anything \
+   -config "$X509_CA_CONFIG_FILE" \
+   -out "$cert_file" \
+	 -extensions v3_ext \
+	 -extfile "$exten_file" \
+   -infiles "$request_file" 
+
+  retcode=$?
+	echo "Sign X509 Req RetCode=$retcode"
+
+	echo "X509_${env_name}_CONFIG_FILE=\"$config_file\"" >> "$X509_CA_ENV_FILE"
+	echo "X509_${env_name}_EXTEN_FILE=\"$exten_file\"" >> "$X509_CA_ENV_FILE"
+	echo "X509_${env_name}_CERT_FILE=\"$cert_file\"" >> "$X509_CA_ENV_FILE"
+	echo "X509_${env_name}_KEY_FILE=\"$key_file\"" >> "$X509_CA_ENV_FILE"
+	echo "X509_${env_name}_REQUEST_FILE=\"$request_file\"" >> "$X509_CA_ENV_FILE"
+}
+
+x509v3_dump_env()
+{
+	tmp_file="$(mktemp)"
+	cat "$X509_CA_ENV_FILE" > "$tmp_file"
+	env | grep -E '^X509_' >> "$tmp_file"
+	set | grep -E '^X509_' >> "$tmp_file"
+	sort "$tmp_file" | uniq | sed 's/^/export /' > "$X509_CA_ENV_FILE"
+	touch "$X509_CA_DONE_FILE"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/initdb.d/10-create-roles.sh b/infrastructure/cdn-in-a-box/traffic_ops/initdb.d/10-create-roles.sh
index ea33476c6..95715356d 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/initdb.d/10-create-roles.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/initdb.d/10-create-roles.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/initdb.d/90-load-dumps.sh b/infrastructure/cdn-in-a-box/traffic_ops/initdb.d/90-load-dumps.sh
index fe35383df..e77d9830e 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/initdb.d/90-load-dumps.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/initdb.d/90-load-dumps.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/run-go.sh b/infrastructure/cdn-in-a-box/traffic_ops/run-go.sh
index 66b03fa74..374ab44a1 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/run-go.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/run-go.sh
@@ -29,10 +29,6 @@
 # DB_NAME
 # ADMIN_USER
 # ADMIN_PASS
-# CERT_COUNTRY
-# CERT_STATE
-# CERT_CITY
-# CERT_COMPANY
 # DOMAIN
 
 # TODO:  Unused -- should be removed?  TRAFFIC_VAULT_PASS
@@ -40,19 +36,27 @@
 # Check that env vars are set
 
 set -x
-envvars=( DB_SERVER DB_PORT DB_ROOT_PASS DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS CERT_COUNTRY CERT_STATE CERT_CITY CERT_COMPANY DOMAIN)
+envvars=( DB_SERVER DB_PORT DB_ROOT_PASS DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS)
 for v in $envvars
 do
 	if [[ -z $$v ]]; then echo "$v is unset"; exit 1; fi
 done
 
+# Source to-access functions and FQDN vars
+source /to-access.sh
+
+until [ -f "$X509_CA_DONE_FILE" ] ; do
+   echo "Waiting on SSL certificate generation."
+   sleep 2
+done
+
 # Write config files
 if [[ -x /config.sh ]]; then
 	/config.sh
 fi
 
-while ! nc trafficops-perl 60443 </dev/null 2>/dev/null; do
-        echo "waiting for trafficops-perl:60443"
+while ! nc "$TO_PERL_FQDN" $TO_PERL_PORT </dev/null 2>/dev/null; do
+        echo "waiting for $TO_PERL_FQDN:$TO_PERL_PORT" 
         sleep 3
 done
 
@@ -60,13 +64,60 @@ cd /opt/traffic_ops/app
 
 CDNCONF=/opt/traffic_ops/app/conf/cdn.conf
 DBCONF=/opt/traffic_ops/app/conf/production/database.conf
+RIAKCONF=/opt/traffic_ops/app/conf/production/riak.conf
 mkdir -p /var/log/traffic_ops
 touch /var/log/traffic_ops/traffic_ops.log
 
-. /to-access.sh
-
 # enroll in the background so traffic_ops_golang can run in foreground
 TO_USER=$TO_ADMIN_USER TO_PASSWORD=$TO_ADMIN_PASSWORD to-enroll $(hostname -s) &
 
-./bin/traffic_ops_golang -cfg $CDNCONF -dbcfg $DBCONF
+./bin/traffic_ops_golang -cfg $CDNCONF -dbcfg $DBCONF -riakcfg $RIAKCONF &
+
+to-enroll "to" ALL || (while true; do echo "enroll failed."; sleep 3 ; done)
+
+### Workaround: Start DeliveryService and Edge association
+while true; do
+  edge_name="$(to-get 'api/1.3/servers/hostname/edge/details' 2>/dev/null | jq -r -c '.response|.hostName')"
+  ds_name="$(to-get 'api/1.3/deliveryservices' 2>/dev/null | jq -r -c '.response[].xmlId')"
+
+  if [ -n "$edge_name" ] && [ "$ds_name" ] ; then
+    tmp_file=$(mktemp)
+    echo "{ \"xmlId\" : \"$ds_name\", \"serverNames\": [ \"$edge_name\" ] }" > $tmp_file
+    cp $tmp_file /shared/enroller/deliveryservice_servers/
+    break
+  else 
+    echo "Waiting for delivery service and edge to exist..."
+  fi
+
+  sleep 2
+done
+
+while true; do
+  echo "Verifying that edge was associated to delivery service..."
+
+  edge_name="$(to-get 'api/1.3/servers/hostname/edge/details' 2>/dev/null | jq -r -c '.response|.hostName')"
+  ds_name="$(to-get 'api/1.3/deliveryservices' 2>/dev/null | jq -r -c '.response[].xmlId')"
+  ds_id="$(to-get 'api/1.3/deliveryservices' 2>/dev/null | jq -r -c '.response[].id')"
+  edge_verify=$(to-get "/api/1.2/deliveryservices/$ds_id/servers" | jq -r '.response[]|.hostName')
+
+  if [[ $edge_verify = $edge_name ]] ; then
+    break
+  fi
+
+  sleep 2
+done
+
+# Snapshot the CDN 
+until [ $(to-get '/CRConfig-Snapshots/CDN-in-a-Box/CRConfig.json' 2>/dev/null | jq -c -e '.config|length') -gt 0 ] ; do 
+  sleep 2
+  echo "Do Snapshot for CDN-in-a-Box..."; 
+  to-put 'api/1.3/cdns/2/snapshot'
+done
+
+# Queue Updates 
+sleep 1
+to-post 'api/1.3/cdns/2/queue_update' '{"action":"queue"}'
+
+### Workaround: End DeliveryService and Edge association
 
+exec tail -f /dev/null
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/run.sh b/infrastructure/cdn-in-a-box/traffic_ops/run.sh
index 3c12d9641..5a164c451 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/run.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/run.sh
@@ -30,21 +30,29 @@
 # DB_NAME
 # ADMIN_USER
 # ADMIN_PASS
-# CERT_COUNTRY
-# CERT_STATE
-# CERT_CITY
-# CERT_COMPANY
-# DOMAIN
-
 # TODO:  Unused -- should be removed?  TRAFFIC_VAULT_PASS
 
 # Check that env vars are set
-envvars=( DB_SERVER DB_PORT DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS CERT_COUNTRY CERT_STATE CERT_CITY CERT_COMPANY DOMAIN)
+envvars=( DB_SERVER DB_PORT DB_USER DB_USER_PASS ADMIN_USER ADMIN_PASS X509_CA_DIR TLD_DOMAIN INFRA_SUBDOMAIN CDN_SUBDOMAIN DS_HOSTS)
 for v in $envvars
 do
 	if [[ -z $$v ]]; then echo "$v is unset"; exit 1; fi
 done
 
+# Source to-access functions and FQDN vars
+source /to-access.sh
+
+# Create SSL certificates and trust the shared CA.
+source /generate-certs.sh
+if x509v3_init; then
+		x509v3_create_cert "$INFRA_SUBDOMAIN" "$INFRA_FQDN"
+		for ds in $DS_HOSTS
+		do
+			x509v3_create_cert "$ds" "$ds.$CDN_FQDN"
+		done
+		x509v3_dump_env
+fi
+
 # Write config files
 set -x
 if [[ -r /config.sh ]]; then
@@ -62,7 +70,7 @@ while ! $pg_isready -h$DB_SERVER -p$DB_PORT -d $DB_NAME; do
         sleep 3
 done
 
-TO_DIR=/opt/traffic_ops/app
+export TO_DIR=/opt/traffic_ops/app
 cat conf/production/database.conf
 
 export PERL5LIB=$TO_DIR/lib:$TO_DIR/local/lib/perl5
@@ -78,11 +86,17 @@ cd $TO_DIR && \
 
 cd $TO_DIR && $TO_DIR/local/bin/hypnotoad script/cdn
 
-export TO_USER=$TO_ADMIN_USER
-export TO_PASSWORD=$TO_ADMIN_PASSWORD
-. /to-access.sh
-to-enroll $(hostname -s)
+until [[ -f ${ENROLLER_DIR}/enroller-started ]]; do
+    echo "waiting for enroller"
+    sleep 3
+done
 
 # Add initial data to traffic ops
 /trafficops-init.sh
+
+export TO_USER=$TO_ADMIN_USER
+export TO_PASSWORD=$TO_ADMIN_PASSWORD
+
+to-enroll "to" ALL || (while true; do echo "enroll failed."; sleep 3 ; done)
+
 exec tail -f /var/log/traffic_ops/traffic_ops.log
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/to-access.sh b/infrastructure/cdn-in-a-box/traffic_ops/to-access.sh
index b1b96731d..d54fcb1e1 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops/to-access.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/to-access.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 #
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
@@ -18,18 +18,32 @@
 # under the License.
 #
 # Defines bash functions to consistently interact with the Traffic Ops API
+#
+# Build FQDNs
+export CDN_FQDN="$CDN_SUBDOMAIN.$TLD_DOMAIN"
+export INFRA_FQDN="$INFRA_SUBDOMAIN.$TLD_DOMAIN"
+export DB_FQDN="$DB_SERVER.$INFRA_FQDN"
+export DNS_FQDN="$DNS_SERVER.$INFRA_FQDN"
+export EDGE_FQDN="$EDGE_HOST.$INFRA_FQDN"
+export MID_FQDN="$MID_HOST.$INFRA_FQDN"
+export ORIGIN_FQDN="$ORIGIN_HOST.$INFRA_FQDN"
+export TO_FQDN="$TO_HOST.$INFRA_FQDN"
+export TO_PERL_FQDN="$TO_PERL_HOST.$INFRA_FQDN"
+export TM_FQDN="$TM_HOST.$INFRA_FQDN"
+export TP_FQDN="$TP_HOST.$INFRA_FQDN"
+export TR_FQDN="$TR_HOST.$INFRA_FQDN"
+export TS_FQDN="$TS_HOST.$INFRA_FQDN"
+export TV_FQDN="$TV_HOST.$INFRA_FQDN"
 
-
-export TO_URL=${TO_URL:-https://$TO_HOST:$TO_PORT}
+export TO_URL=${TO_URL:-https://$TO_FQDN:$TO_PORT}
 export TO_USER=${TO_USER:-$TO_ADMIN_USER}
 export TO_PASSWORD=${TO_PASSWORD:-$TO_ADMIN_PASSWORD}
 
 export CURLOPTS=${CURLOPTS:--LfsS}
 export CURLAUTH=${CURLAUTH:--k}
-export COOKIEJAR=$(mktemp -t XXXX.cookie)
-
+export COOKIEJAR=$(mktemp)
 
-login=$(mktemp -t XXXX.login)
+login=$(mktemp)
 
 cleanup() {
 	rm -f "$COOKIEJAR" "$login"
@@ -82,15 +96,20 @@ to-get() {
 }
 
 to-post() {
+	local t
+	local data
 	if [[ -z "$2" ]]; then
 		data=""
 	elif [[ -f "$2" ]]; then
 		data="--data @$2"
 	else
-		data="--data $2"
+		t=$(mktemp)
+		echo $2 >$t
+		data="--data @$t"
 	fi
 	to-auth && \
 	    curl $CURLAUTH $CURLOPTS --cookie "$COOKIEJAR" -X POST $data "$TO_URL/$1"
+	[[ -n $t ]] && rm "$t"    
 }
 
 to-put() {
@@ -110,13 +129,138 @@ to-delete() {
 		curl $CURLAUTH $CURLOPTS --cookie "$COOKIEJAR" -X DELETE "$TO_URL/$1"
 }
 
+# Constructs a server's JSON definiton and places it into the enroller's structure for loading
+# args:
+#         serverType - the type of the server to be created; one of "edge", "mid", "tm", "origin"
 to-enroll() {
-    local service=$1
-    until nc enroller 443 </dev/null >/dev/null 2>&1; do 
-        echo "waiting for enroller"
-        sleep 5
-    done
-
-    action=${service:+?name=$service}
-    curl -k -X POST https://enroller${action}
+
+	while true; do 
+		[ -d "$ENROLLER_DIR" ] && break
+		echo "Waiting for $ENROLLER_DIR ..."
+		sleep 2
+	done
+
+	while true; do 
+		[ "$serverType" = "to" ] && break
+		[ -f "$ENROLLER_DIR/initial-load-done" ] && break
+		echo "Waiting for traffic-ops to do initial load ..."
+		sleep 2
+	done
+	if [[ ! -d ${ENROLLER_DIR}/servers ]]; then
+		echo "${ENROLLER_DIR}/servers not found -- contents:"
+		find ${ENROLLER_DIR} -ls
+	fi
+	local serverType="$1"
+
+	if [[ ! -z "$2" ]]; then
+		export MY_CDN="$2"
+	else
+		export MY_CDN="CDN-in-a-Box"
+	fi
+
+
+	if [[ "$serverType" == "origin" ]]; then
+		cat <<-EOORIGIN >"$ENROLLER_DIR/origins/$HOSTNAME.json"
+		{
+			"deliveryService": "demo1",
+			"fqdn": "$HOSTNAME",
+			"name": "origin",
+			"protocol": "http",
+			"tenant": "root"
+		}
+		EOORIGIN
+		return 0
+	fi
+
+	export MY_NET_INTERFACE='eth0'
+	export MY_HOSTNAME="$(hostname -s)"
+	export MY_DOMAINNAME="$(dnsdomainname)"
+	export MY_IP="$(ifconfig $MY_NET_INTERFACE | grep 'inet ' | tr -s ' ' | cut -d ' ' -f 3)"
+	export MY_GATEWAY="$(route -n | grep $MY_NET_INTERFACE | grep -E '^0\.0\.0\.0' | tr -s ' ' | cut -d ' ' -f2)"
+	export MY_NETMASK="$(ifconfig $MY_NET_INTERFACE | grep 'inet ' | tr -s ' ' | cut -d ' ' -f 5)"
+	export MY_IP6_ADDRESS="$(ifconfig $MY_NET_INTERFACE | grep inet6 | grep global | awk '{ print $2 }')"
+	export MY_IP6_GATEWAY="$(route -n6 | grep UG | awk '{print $2}')"
+
+	case "$serverType" in
+		"edge" )
+			export MY_TYPE="EDGE"
+			export MY_PROFILE="ATS_EDGE_TIER_CACHE"
+			export MY_STATUS="REPORTED"
+			if [[ ! -z "$3" ]]; then
+				export MY_CACHE_GROUP="$3"
+			else
+				export MY_CACHE_GROUP="CDN_in_a_Box_Edge"
+			fi
+			;;
+		"mid" )
+			export MY_TYPE="MID"
+			export MY_PROFILE="ATS_MID_TIER_CACHE"
+			export MY_STATUS="REPORTED"
+			if [[ ! -z "$3" ]]; then
+				export MY_CACHE_GROUP="$3"
+			else
+				export MY_CACHE_GROUP="CDN_in_a_Box_Mid"
+			fi
+			;;
+		"tm" )
+			export MY_TYPE="RASCAL"
+			export MY_PROFILE="RASCAL-Traffic_Monitor"
+			export MY_STATUS="ONLINE"
+			if [[ ! -z "$3" ]]; then
+				export MY_CACHE_GROUP="$3"
+			else
+				export MY_CACHE_GROUP="CDN_in_a_Box_Edge"
+			fi
+			;;
+		"to" ) 
+			export MY_TYPE="TRAFFIC_OPS"
+			export MY_PROFILE="TRAFFIC_OPS"
+			export MY_STATUS="ONLINE"
+			if [[ ! -z "$3" ]]; then
+				export MY_CACHE_GROUP="$3"
+			else
+				export MY_CACHE_GROUP="CDN_in_a_Box_Edge"
+			fi
+			;;
+		"tr" )
+			export MY_TYPE="CCR"
+			export MY_PROFILE="CCR_CIAB"
+			export MY_STATUS="ONLINE"
+			if [[ ! -z "$3" ]]; then
+				export MY_CACHE_GROUP="$3"
+			else
+				export MY_CACHE_GROUP="CDN_in_a_Box_Edge"
+			fi
+			;;
+		"tp" )
+			export MY_TYPE="TRAFFIC_PORTAL"
+			export MY_PROFILE="TRAFFIC_PORTAL"
+			export MY_STATUS="ONLINE"
+			if [[ ! -z "$3" ]]; then
+				export MY_CACHE_GROUP="$3"
+			else
+				export MY_CACHE_GROUP="CDN_in_a_Box_Edge"
+			fi
+			;;
+		"tv" )
+			export MY_TYPE="RIAK"
+			export MY_PROFILE="RIAK_ALL"
+			export MY_STATUS="ONLINE"
+			if [[ ! -z "$3" ]]; then
+				export MY_CACHE_GROUP="$3"
+			else
+				export MY_CACHE_GROUP="CDN_in_a_Box_Edge"
+			fi
+			;;
+		* )
+			echo "Usage: to-enroll SERVER_TYPE" >&2
+			echo "(SERVER_TYPE must be a recognized server type)" >&2
+			return 1
+			;;
+	esac
+
+	# replace env references in the file
+	envsubst < "/server_template.json" > "${ENROLLER_DIR}/servers/$HOSTNAME.json"
+
+	sleep 3
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/trafficops-init.sh b/infrastructure/cdn-in-a-box/traffic_ops/trafficops-init.sh
index 7d955a14c..e72838cc1 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/trafficops-init.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/trafficops-init.sh
@@ -1,5 +1,5 @@
-#!/bin/bash
-
+#!/usr/bin/env bash
+#
 # 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
@@ -28,53 +28,44 @@ done
 
 . /to-access.sh
 
-TO_URL="https://$TO_HOST:$TO_PORT"
+TO_URL="https://$TO_FQDN:$TO_PORT"
 # wait until the ping endpoint succeeds
 while ! to-ping 2>/dev/null; do
    echo waiting for trafficops
    sleep 3
 done
 
-# NOTE: order dependent on foreign key references, e.g. tenants must be defined before users
-endpoints="cdns divisions regions phys_locations tenants users cachegroups deliveryservices"
+# NOTE: order dependent on foreign key references, e.g. profiles must be loaded before parameters
+endpoints="cdns types divisions regions phys_locations tenants users cachegroups deliveryservices profiles parameters servers deliveryservice_servers"
+vars=$(awk -F = '/^\w/ {printf "$%s ",$1}' /variables.env)
 
 load_data_from() {
     local dir="$1"
     if [[ ! -d $dir ]] ; then
         echo "Failed to load data from '$dir': directory does not exist"
     fi
-
+    cd "$dir"
     local status=0
-    for ep in $endpoints; do
-        d="$dir/$ep"
+    for d in $endpoints; do
         [[ -d $d ]] || continue
-        echo "Loading data from $d"
-        for f in "$d"/*.json; do
-            [[ -r $f ]] || continue
-            t=$(mktemp --tmpdir $ep-XXX.json)
-            envsubst <"$f" >"$t"
-            if ! to-post api/1.3/"$ep" "$t"; then
-                echo POST api/1.3/"$ep" "$t" failed
-                status=$?
-            fi
-            rm "$t"
+        for f in "$d"/*.json; do 
+            echo "Loading $f"
+            envsubst "$vars" <$f  > "$ENROLLER_DIR"/$f
         done
     done
     if [[ $status -ne 0 ]]; then
         exit $status
     fi
-
-
+    # After done loading all data
+    touch "$ENROLLER_DIR/initial-load-done"
+    cd -
 }
 
 # First,  load required data at the top level
 load_data_from /traffic_ops_data
 
-# If TO_DATA is defined, load from subdirs with that name (space-separated)
-if [[ -n $TO_DATA ]]; then
-    for subdir in $TO_DATA; do
-        load_data_from /traffic_ops_data/$subdir
-    done
-fi
-
-
+# Copy the free MaxMind GeoLite DB to TrafficOps public directory
+tar -C /var/tmp -zxpvf /GeoLite2-City.tar.gz
+geo_dir=$(find /var/tmp -maxdepth 1 -type d -name GeoLite2-City\*)
+gzip -c "$geo_dir/GeoLite2-City.mmdb" > "$TO_DIR/public/GeoLite2-City.mmdb.gz"
+chown trafops:trafops "$TO_DIR/public/GeoLite2-City.mmdb.gz"
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/010-TRAFFIC_ANALYTICS.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/010-TRAFFIC_ANALYTICS.json
new file mode 100644
index 000000000..c22105e8d
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/010-TRAFFIC_ANALYTICS.json
@@ -0,0 +1,7 @@
+{
+  "latitude": 38.897663,
+  "longitude": -77.036574,
+  "name": "TRAFFIC_ANALYTICS",
+  "shortName": "TRAFFIC_ANALYTICS",
+  "typeName": "TC_LOC"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/020-TRAFFIC_OPS.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/020-TRAFFIC_OPS.json
new file mode 100644
index 000000000..ee4319986
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/020-TRAFFIC_OPS.json
@@ -0,0 +1,7 @@
+{
+  "latitude": 38.897663,
+  "longitude": -77.036574,
+  "name": "TRAFFIC_OPS",
+  "shortName": "TRAFFIC_OPS",
+  "typeName": "TC_LOC"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/030-TRAFFIC_OPS_DB.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/030-TRAFFIC_OPS_DB.json
new file mode 100644
index 000000000..653ccd6eb
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/030-TRAFFIC_OPS_DB.json
@@ -0,0 +1,7 @@
+{
+  "latitude": 38.897663,
+  "longitude": -77.036574,
+  "name": "TRAFFIC_OPS_DB",
+  "shortName": "TRAFFIC_OPS_DB",
+  "typeName": "TC_LOC"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/040-TRAFFIC_PORTAL.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/040-TRAFFIC_PORTAL.json
new file mode 100644
index 000000000..00435db03
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/040-TRAFFIC_PORTAL.json
@@ -0,0 +1,7 @@
+{
+  "latitude": 38.897663,
+  "longitude": -77.036574,
+  "name": "TRAFFIC_PORTAL",
+  "shortName": "TRAFFIC_PORTAL",
+  "typeName": "TC_LOC"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/050-TRAFFIC_STATS.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/050-TRAFFIC_STATS.json
new file mode 100644
index 000000000..a5b1d6178
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/050-TRAFFIC_STATS.json
@@ -0,0 +1,7 @@
+{
+  "latitude": 38.897663,
+  "longitude": -77.036574,
+  "name": "TRAFFIC_STATS",
+  "shortName": "TRAFFIC_STATS",
+  "typeName": "TC_LOC"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/060-ciabMid.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/060-ciabMid.json
new file mode 100644
index 000000000..0b98a6b01
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/060-ciabMid.json
@@ -0,0 +1,7 @@
+{
+  "latitude": 38.897663,
+  "longitude": -77.036574,
+  "name": "CDN_in_a_Box_Mid",
+  "shortName": "ciabMid",
+  "typeName": "MID_LOC"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/070-ciabEdge.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/070-ciabEdge.json
new file mode 100644
index 000000000..8c3a235e3
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/070-ciabEdge.json
@@ -0,0 +1,8 @@
+{
+  "latitude": 38.897663,
+  "longitude": -77.036574,
+  "name": "CDN_in_a_Box_Edge",
+  "parentCachegroupName": "CDN_in_a_Box_Mid",
+  "shortName": "ciabEdge",
+  "typeName": "EDGE_LOC"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/edge_cachegroup.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/edge_cachegroup.json
deleted file mode 100644
index 3f0b0ce7d..000000000
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/edge_cachegroup.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-"name": "CDN_in_a_Box_Edge",
-"shortName": "ciabEdge",
-"latitude": 38.897663,
-"longitude": -77.036574,
-"typeId": 23
-}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/mid_cachegroup.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/mid_cachegroup.json
deleted file mode 100644
index 00c9e8406..000000000
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/cachegroups/mid_cachegroup.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-"name": "CDN_in_a_Box_Mid",
-"shortName": "ciabMid",
-"latitude": 38.897663,
-"longitude": -77.036574,
-"typeId": 24
-}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/cdns/cdns.json b/infrastructure/cdn-in-a-box/traffic_ops_data/cdns/010-CDN-in-a-Box.json
similarity index 60%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/cdns/cdns.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/cdns/010-CDN-in-a-Box.json
index 00fa3ac9c..549df921c 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/cdns/cdns.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/cdns/010-CDN-in-a-Box.json
@@ -1,5 +1,5 @@
 {
 	"dnssecEnabled": false,
-	"domainName": "cdn.local",
+	"domainName": "mycdn.ciab.test",
 	"name": "CDN-in-a-Box"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservice_servers/010-demo1.json b/infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservice_servers/010-demo1.json
new file mode 100644
index 000000000..6c2a4fb85
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservice_servers/010-demo1.json
@@ -0,0 +1,6 @@
+{
+  "xmlId": "demo1",
+  "serverNames": [
+    "edge"
+  ]
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservices/deliveryservices.json b/infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservices/010-ciab.json
similarity index 51%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservices/deliveryservices.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservices/010-ciab.json
index 2a59beeb8..28ee3a705 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservices/deliveryservices.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/deliveryservices/010-ciab.json
@@ -1,24 +1,27 @@
 {
-    "xmlId": "ciab",
-    "displayName": "CDN in a Box",
-    "tenantId": 1,
+    "xmlId": "demo1",
+    "displayName": "Demo 1",
+    "tenantName": "root",
     "protocol": 0,
-    "orgServerFqdn": "http://origin",
-    "cdnId": 2,
-    "typeId": 5,
+    "orgServerFqdn": "http://origin.infra.ciab.test",
+    "cdnName": "CDN-in-a-Box",
+    "type": "HTTP",
     "active": true,
     "dscp": 0,
     "geoLimit": 0,
     "geoProvider": 0,
+    "initialDispersion": 1,
     "ipv6RoutingEnabled": true,
     "logsEnabled": true,
+    "longDesc": "Apachecon North America 2018",
     "multiSiteOrigin": false,
     "missLat": 42.0,
     "missLong": -88.0,
+    "profileName": "",
     "qstringIgnore": 0,
     "rangeRequestHandling": 0,
     "regionalGeoBlocking": false,
-    "routingName": "cdn",
+    "routingName": "video",
     "anonymousBlockingEnabled": false,
-    "signingAlgorithm": null
+    "signingAlgorithm": "url_sig"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/divisions/north.json b/infrastructure/cdn-in-a-box/traffic_ops_data/divisions/010-north.json
similarity index 100%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/divisions/north.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/divisions/010-north.json
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/divisions/020-usa.json b/infrastructure/cdn-in-a-box/traffic_ops_data/divisions/020-usa.json
new file mode 100644
index 000000000..4c5db83bc
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/divisions/020-usa.json
@@ -0,0 +1,3 @@
+{
+  "name": "USA"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/divisions/usa.json b/infrastructure/cdn-in-a-box/traffic_ops_data/divisions/usa.json
deleted file mode 100644
index 4a6c163a5..000000000
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/divisions/usa.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "name": "U.S.A"
-}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/parameters/010-global b/infrastructure/cdn-in-a-box/traffic_ops_data/parameters/010-global
new file mode 100644
index 000000000..18e96c3a3
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/parameters/010-global
@@ -0,0 +1,53 @@
+   [
+      {
+         "value" : "https://trafficops.infra.ciab.test:443/",
+         "name" : "tm.url",
+         "config_file" : "global"
+      },
+      {
+         "value" : "/images/tc_logo.png",
+         "config_file" : "global",
+         "name" : "tm.logourl"
+      },
+      {
+         "config_file" : "global",
+         "name" : "tm.instance_name",
+         "value" : "CDN-In-A-Box"
+      },
+      {
+         "value" : "https://trafficops.infra.ciab.test/GeoLiteCity.dat.gz",
+         "config_file" : "CRConfig.json",
+         "name" : "geolocation.polling.url"
+      },
+      {
+         "name" : "geolocation6.polling.url",
+         "config_file" : "CRConfig.json",
+         "value" : "https://trafficops.infra.ciab.test/GeoLiteCity.dat.gz"
+      },
+      {
+         "value" : "Traffic Ops",
+         "config_file" : "global",
+         "name" : "tm.toolname"
+      },
+      {
+         "value" : "0",
+         "config_file" : "global",
+         "name" : "use_reval_pending"
+      },
+      {
+         "name" : "use_tenancy",
+         "config_file" : "global",
+         "value" : "1"
+      },
+      {
+         "name" : "default_geo_miss_latitude",
+         "config_file" : "global",
+         "value" : "0"
+      },
+      {
+         "name" : "default_geo_miss_longitude",
+         "config_file" : "global",
+         "value" : "-1"
+      }
+   ]
+
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/apachecon.json b/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/010-apachecon.json
similarity index 88%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/apachecon.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/010-apachecon.json
index 23ea3408c..0160fbde9 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/apachecon.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/010-apachecon.json
@@ -5,5 +5,5 @@
   "city": "Montreal QC",
   "state": "Canada",
   "zip": "H3B 4C9",
-  "regionId": 2
+  "region": "Montreal"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/020-phys_location.json b/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/020-phys_location.json
new file mode 100644
index 000000000..56f624db4
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/020-phys_location.json
@@ -0,0 +1,9 @@
+{
+  "name": "CDN_in_a_Box",
+  "shortName": "ciab",
+  "address": "1600 Pennsylvania Avenue NW",
+  "city": "Washington",
+  "state": "DC",
+  "zip": "20500",
+  "region": "Washington, D.C"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/phys_location.json b/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/phys_location.json
deleted file mode 100644
index ffa588047..000000000
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/phys_locations/phys_location.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-	"name": "CDN_in_a_Box",
-	"shortName": "ciab",
-	"address": "1600 Pennsylvania Avenue NW",
-	"city": "Washington",
-	"state": "D.C.",
-	"zip": "20500",
-	"regionId": 1
-}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/010-ATS_EDGE_TIER_CACHE.json b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/010-ATS_EDGE_TIER_CACHE.json
new file mode 100644
index 000000000..4d161d1d8
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/010-ATS_EDGE_TIER_CACHE.json
@@ -0,0 +1,1600 @@
+{
+	"cdnName": "CDN-in-a-Box",
+	"description": "Edge Cache - Apache Traffic Server",
+	"name": "ATS_EDGE_TIER_CACHE",
+	"routingDisabled": false,
+	"type": "ATS_PROFILE",
+	"params": [
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.proxy_name",
+		"secure": false,
+		"value": "STRING __HOSTNAME__"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.config_dir",
+		"secure": false,
+		"value": "STRING /etc/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.proxy_binary_opts",
+		"secure": false,
+		"value": "STRING -M"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.temp_dir",
+		"secure": false,
+		"value": "STRING /tmp"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.exec_thread.autoconfig.scale",
+		"secure": false,
+		"value": "FLOAT 1.5"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.accept_threads",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.admin.user_id",
+		"secure": false,
+		"value": "STRING ats"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.admin.autoconf_port",
+		"secure": false,
+		"value": "INT 8083"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.process_manager.mgmt_port",
+		"secure": false,
+		"value": "INT 8084"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.alarm.abs_path",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.server_ports",
+		"secure": false,
+		"value": "STRING 80 80:ipv6"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_ports",
+		"secure": false,
+		"value": "STRING 443 563"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.insert_request_via_str",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.insert_response_via_str",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.response_server_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.insert_age_in_response",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.enable_url_expandomatic",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.no_dns_just_forward_to_parent",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.uncacheable_requests_bypass_parent",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.keep_alive_enabled_in",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.keep_alive_enabled_out",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.chunking_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.send_http11_requests",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.share_server_sessions",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.origin_server_pipeline",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.user_agent_pipeline",
+		"secure": false,
+		"value": "INT 8"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.referer_filter",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.referer_format_redirect",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.referer_default_redirect",
+		"secure": false,
+		"value": "STRING http://www.example.com/"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy_routing_enable",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.retry_time",
+		"secure": false,
+		"value": "INT 60"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.forward.proxy_auth_to_parent",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.keep_alive_no_activity_timeout_in",
+		"secure": false,
+		"value": "INT 115"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.keep_alive_no_activity_timeout_out",
+		"secure": false,
+		"value": "INT 120"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.transaction_no_activity_timeout_in",
+		"secure": false,
+		"value": "INT 30"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.transaction_no_activity_timeout_out",
+		"secure": false,
+		"value": "INT 30"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.transaction_active_timeout_out",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.accept_no_activity_timeout",
+		"secure": false,
+		"value": "INT 120"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_attempts_max_retries",
+		"secure": false,
+		"value": "INT 6"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_attempts_max_retries_dead_server",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_attempts_rr_retries",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_attempts_timeout",
+		"secure": false,
+		"value": "INT 10"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.post_connect_attempts_timeout",
+		"secure": false,
+		"value": "INT 1800"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.down_server.cache_time",
+		"secure": false,
+		"value": "INT 300"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.down_server.abort_threshold",
+		"secure": false,
+		"value": "INT 10"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.congestion_control.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_from",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_referer",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_user_agent",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_cookie",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_client_ip",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_insert_client_ip",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_other_header_list",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.insert_squid_x_forwarded_for",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.push_method_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.http",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_client_no_cache",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ims_on_client_no_cache",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_server_no_cache",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.normalize_ae_gzip",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.cache_responses_to_cookies",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_authentication",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.cache_urls_that_look_dynamic",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.enable_default_vary_headers",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.when_to_revalidate",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.when_to_add_no_cache_to_msie_requests",
+		"secure": false,
+		"value": "INT -1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.required_headers",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.max_stale_age",
+		"secure": false,
+		"value": "INT 604800"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.range.lookup",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.heuristic_min_lifetime",
+		"secure": false,
+		"value": "INT 3600"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.heuristic_max_lifetime",
+		"secure": false,
+		"value": "INT 86400"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.heuristic_lm_factor",
+		"secure": false,
+		"value": "FLOAT 0.10"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.fuzz.time",
+		"secure": false,
+		"value": "INT 240"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.fuzz.probability",
+		"secure": false,
+		"value": "FLOAT 0.005"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.vary_default_text",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.vary_default_images",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.vary_default_other",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.enable_http_stats",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.body_factory.enable_logging",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.body_factory.response_suppression_mode",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.connections_throttle",
+		"secure": false,
+		"value": "INT 500000"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.defer_accept",
+		"secure": false,
+		"value": "INT 45"
+	},
+	{
+		"configFile": "records.config",
+		"name": "LOCAL proxy.local.cluster.type",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.cluster_port",
+		"secure": false,
+		"value": "INT 8086"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.rsport",
+		"secure": false,
+		"value": "INT 8088"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.mcport",
+		"secure": false,
+		"value": "INT 8089"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.mc_group_addr",
+		"secure": false,
+		"value": "STRING 224.0.1.37"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.mc_ttl",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.log_bogus_mc_msgs",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.ethernet_interface",
+		"secure": false,
+		"value": "STRING lo"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.permit.pinning",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.ram_cache.algorithm",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.ram_cache.use_seen_filter",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.ram_cache.compress",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.limits.http.max_alts",
+		"secure": false,
+		"value": "INT 5"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.max_doc_size",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.mutex_retry_delay",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.search_default_domains",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.splitDNS.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.max_dns_in_flight",
+		"secure": false,
+		"value": "INT 2048"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.url_expansions",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.round_robin_nameservers",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.nameservers",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.resolv_conf",
+		"secure": false,
+		"value": "STRING /etc/resolv.conf"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.validate_query_name",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.timeout",
+		"secure": false,
+		"value": "INT 1440"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.strict_round_robin",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.logging_enabled",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.max_secs_per_buffer",
+		"secure": false,
+		"value": "INT 5"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.max_space_mb_for_logs",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.max_space_mb_for_orphan_logs",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.max_space_mb_headroom",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.hostname",
+		"secure": false,
+		"value": "STRING localhost"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.logfile_dir",
+		"secure": false,
+		"value": "STRING /var/log/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.logfile_perm",
+		"secure": false,
+		"value": "STRING rw-r--r--"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.custom_logs_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.squid_log_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.common_log_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.extended_log_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.separate_icp_logs",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.separate_host_logs",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "LOCAL proxy.local.log.collation_mode",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_host",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_port",
+		"secure": false,
+		"value": "INT 8085"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_secret",
+		"secure": false,
+		"value": "STRING foobar"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_host_tagged",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_retry_sec",
+		"secure": false,
+		"value": "INT 5"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.rolling_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.rolling_interval_sec",
+		"secure": false,
+		"value": "INT 86400"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.rolling_offset_hr",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.rolling_size_mb",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.auto_delete_rolled_files",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.sampling_frequency",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.reverse_proxy.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.header.parse.no_host_url_redirect",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.default_to_server_pac",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.default_to_server_pac_port",
+		"secure": false,
+		"value": "INT -1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.pristine_host_hdr",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.number.threads",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.SSLv2",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.TLSv1",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.compression",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.certification_level",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.cert_chain.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.CA.cert.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.verify.server",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.cert.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.private_key.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.CA.cert.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.icp_interface",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.icp_port",
+		"secure": false,
+		"value": "INT 3130"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.multicast_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.query_timeout",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.force",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.retry_count",
+		"secure": false,
+		"value": "INT 10"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.retry_interval",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.concurrent_updates",
+		"secure": false,
+		"value": "INT 100"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.sock_send_buffer_size_in",
+		"secure": false,
+		"value": "INT 262144"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.sock_recv_buffer_size_in",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.sock_send_buffer_size_out",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.sock_recv_buffer_size_out",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.core_limit",
+		"secure": false,
+		"value": "INT -1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.debug.enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.debug.tags",
+		"secure": false,
+		"value": "STRING http|dns"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dump_mem_info_frequency",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.slow.log.threshold",
+		"secure": false,
+		"value": "INT 10000"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.task_threads",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "cache.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "hosting.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "parent.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "plugin.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "records.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "remap.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "storage.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "volume.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.remap_required",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.file",
+		"secure": false,
+		"value": "STRING parent.config"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.filename",
+		"secure": false,
+		"value": "STRING remap.config"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.cluster_configuration ",
+		"secure": false,
+		"value": "STRING cluster.config"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.threshold.loadavg",
+		"secure": false,
+		"value": "25.0"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.threshold.availableBandwidthInKbps",
+		"secure": false,
+		"value": ">1750000"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "history.count",
+		"secure": false,
+		"value": "30"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.control.filename",
+		"secure": false,
+		"value": "STRING cache.config"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.RollingEnabled",
+		"secure": false,
+		"value": "3"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.RollingIntervalSec",
+		"secure": false,
+		"value": "86400"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.RollingOffsetHr",
+		"secure": false,
+		"value": "11"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.RollingSizeMb",
+		"secure": false,
+		"value": "4"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.xml_config_file",
+		"name": "CONFIG proxy.config.log.xml_configFile",
+		"secure": false,
+		"value": "STRING logs_xml.config"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.threshold.queryTime",
+		"secure": false,
+		"value": "1000"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.polling.url",
+		"secure": false,
+		"value": "http://${hostname}/_astats?application=&inf.name=${interface_name}"
+	},
+	{
+		"configFile": "storage.config",
+		"name": "Disk_Volume",
+		"secure": false,
+		"value": "1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.hosting_filename",
+		"secure": false,
+		"value": "STRING hosting.config"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.connection.timeout",
+		"secure": false,
+		"value": "2000"
+	},
+	{
+		"configFile": "12M_facts",
+		"name": "location",
+		"secure": false,
+		"value": "/opt/ort"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_accept_encoding_mismatch",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "chkconfig",
+		"name": "trafficserver",
+		"secure": false,
+		"value": "0:off\t1:off\t2:on\t3:on\t4:on\t5:on\t6:off"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.debug_filter",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.enable_reclaim",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.max_overage",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.show_location",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.allow_empty_doc",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "LOCAL proxy.config.cache.interim.storage",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "plugin.config",
+		"name": "regex_revalidate.so",
+		"secure": false,
+		"value": "--config regex_revalidate.config"
+	},
+	{
+		"configFile": "regex_revalidate.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.stack_dump_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.ttl_mode",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.lookup_timeout",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.serve_stale_for",
+		"secure": false,
+		"value": "INT 6"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.enable_read_while_writer",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.background_fill_active_timeout",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.background_fill_completed_threshold",
+		"secure": false,
+		"value": "FLOAT 0.0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.extended2_log_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.exec_thread.affinity",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.exec_thread.autoconfig",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.exec_thread.limit",
+		"secure": false,
+		"value": "INT 8"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.thread_freelist_size",
+		"secure": false,
+		"value": "INT 1024"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.mlock_enabled",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogFormat.Format",
+		"secure": false,
+		"value": "%<cqtq> chi=%<chi> phn=%<phn> php=%<php> shn=%<shn> url=%<cquuc> cqhm=%<cqhm> cqhv=%<cqhv> pssc=%<pssc> ttms=%<ttms> b=%<pscl> sssc=%<sssc> sscl=%<sscl> cfsc=%<cfsc> pfsc=%<pfsc> crc=%<crc> phr=%<phr> pqsn=%<pqsn> uas=\"%<{User-Agent}cqh>\" "
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogFormat.Name",
+		"secure": false,
+		"value": "custom_ats_2"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.Format",
+		"secure": false,
+		"value": "custom_ats_2"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.Filename",
+		"secure": false,
+		"value": "custom_ats_2"
+	},
+	{
+		"configFile": "plugin.config",
+		"name": "astats_over_http.so",
+		"secure": false,
+		"value": ""
+	},
+	{
+		"configFile": "astats.config",
+		"name": "allow_ip",
+		"secure": false,
+		"value": "127.0.0.1,10.0.0.0/8"
+	},
+	{
+		"configFile": "astats.config",
+		"name": "allow_ip6",
+		"secure": false,
+		"value": "::1/128"
+	},
+	{
+		"configFile": "astats.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver"
+	},
+	{
+		"configFile": "astats.config",
+		"name": "path",
+		"secure": false,
+		"value": "_astats"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.http.compatibility.4-2-0-fixup",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.cipher_suite",
+		"secure": false,
+		"value": "STRING ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2:@STRENGTH"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.honor_cipher_order",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.cert.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.private_key.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.CA.cert.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.private_key.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.cert.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.CA.cert.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.SSLv3",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.multicert.filename",
+		"secure": false,
+		"value": "STRING ssl_multicert.config"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.negative_caching_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "astats.config",
+		"name": "record_types",
+		"secure": false,
+		"value": "122"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.transaction_active_timeout_in",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.body_factory.enable_customizations",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.negative_caching_lifetime",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.body_factory.template_sets_dir",
+		"secure": false,
+		"value": "STRING /etc/trafficserver/body_factory"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.ip_allow.filename",
+		"secure": false,
+		"value": "STRING ip_allow.config"
+	},
+	{
+		"configFile": "parent.config",
+		"name": "weight",
+		"secure": false,
+		"value": "1.2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.fail_threshold",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.connect_attempts_timeout",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.per_parent_connect_attempts",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.total_connect_attempts",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "storage.config",
+		"name": "Drive_Letters",
+		"secure": false,
+		"value": "cache"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.hostdb.sync_frequency",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.storage_size",
+		"secure": false,
+		"value": "INT 2048"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.size",
+		"secure": false,
+		"value": "INT 2048"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.guaranteed_max_lifetime",
+		"secure": false,
+		"value": "INT 2592000"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_client_cc_max_age",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.default_inactivity_timeout",
+		"secure": false,
+		"value": "INT 180"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.mark_down_hostdb",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.dontdump_iobuffers",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "ip_allow.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver"
+	},
+	{
+		"configFile": "storage.config",
+		"name": "Drive_Prefix",
+		"secure": false,
+		"value": "/var/trafficserver/"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.logfile.rolling_size_mb",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.logfile.rolling_enabled",
+		"secure": false,
+		"value": "INT 2"
+	}
+]
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/020-ATS_MID_TIER_CACHE.json b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/020-ATS_MID_TIER_CACHE.json
new file mode 100644
index 000000000..9f7fa201f
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/020-ATS_MID_TIER_CACHE.json
@@ -0,0 +1,1600 @@
+{
+	"cdnName": "CDN-in-a-Box",
+	"description": "Mid Cache - Apache Traffic Server",
+	"name": "ATS_MID_TIER_CACHE",
+	"routingDisabled": false,
+	"type": "ATS_PROFILE",
+	"params": [
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.proxy_name",
+		"secure": false,
+		"value": "STRING __HOSTNAME__"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.config_dir",
+		"secure": false,
+		"value": "STRING /etc/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.proxy_binary_opts",
+		"secure": false,
+		"value": "STRING -M"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.temp_dir",
+		"secure": false,
+		"value": "STRING /tmp"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.exec_thread.autoconfig.scale",
+		"secure": false,
+		"value": "FLOAT 1.5"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.accept_threads",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.admin.user_id",
+		"secure": false,
+		"value": "STRING ats"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.admin.autoconf_port",
+		"secure": false,
+		"value": "INT 8083"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.process_manager.mgmt_port",
+		"secure": false,
+		"value": "INT 8084"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.alarm.abs_path",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.server_ports",
+		"secure": false,
+		"value": "STRING 80 80:ipv6"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_ports",
+		"secure": false,
+		"value": "STRING 443 563"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.insert_request_via_str",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.insert_response_via_str",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.response_server_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.insert_age_in_response",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.enable_url_expandomatic",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.no_dns_just_forward_to_parent",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.uncacheable_requests_bypass_parent",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.keep_alive_enabled_in",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.keep_alive_enabled_out",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.chunking_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.send_http11_requests",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.share_server_sessions",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.origin_server_pipeline",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.user_agent_pipeline",
+		"secure": false,
+		"value": "INT 8"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.referer_filter",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.referer_format_redirect",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.referer_default_redirect",
+		"secure": false,
+		"value": "STRING http://www.example.com/"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy_routing_enable",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.retry_time",
+		"secure": false,
+		"value": "INT 60"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.forward.proxy_auth_to_parent",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.keep_alive_no_activity_timeout_in",
+		"secure": false,
+		"value": "INT 115"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.keep_alive_no_activity_timeout_out",
+		"secure": false,
+		"value": "INT 120"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.transaction_no_activity_timeout_in",
+		"secure": false,
+		"value": "INT 30"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.transaction_no_activity_timeout_out",
+		"secure": false,
+		"value": "INT 30"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.transaction_active_timeout_out",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.accept_no_activity_timeout",
+		"secure": false,
+		"value": "INT 120"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_attempts_max_retries",
+		"secure": false,
+		"value": "INT 6"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_attempts_max_retries_dead_server",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_attempts_rr_retries",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.connect_attempts_timeout",
+		"secure": false,
+		"value": "INT 10"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.post_connect_attempts_timeout",
+		"secure": false,
+		"value": "INT 1800"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.down_server.cache_time",
+		"secure": false,
+		"value": "INT 300"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.down_server.abort_threshold",
+		"secure": false,
+		"value": "INT 10"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.congestion_control.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_from",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_referer",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_user_agent",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_cookie",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_remove_client_ip",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_insert_client_ip",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.anonymize_other_header_list",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.insert_squid_x_forwarded_for",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.push_method_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.http",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_client_no_cache",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ims_on_client_no_cache",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_server_no_cache",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.normalize_ae_gzip",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.cache_responses_to_cookies",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_authentication",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.cache_urls_that_look_dynamic",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.enable_default_vary_headers",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.when_to_revalidate",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.when_to_add_no_cache_to_msie_requests",
+		"secure": false,
+		"value": "INT -1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.required_headers",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.max_stale_age",
+		"secure": false,
+		"value": "INT 604800"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.range.lookup",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.heuristic_min_lifetime",
+		"secure": false,
+		"value": "INT 3600"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.heuristic_max_lifetime",
+		"secure": false,
+		"value": "INT 86400"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.heuristic_lm_factor",
+		"secure": false,
+		"value": "FLOAT 0.10"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.fuzz.time",
+		"secure": false,
+		"value": "INT 240"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.fuzz.probability",
+		"secure": false,
+		"value": "FLOAT 0.005"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.vary_default_text",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.vary_default_images",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.vary_default_other",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.enable_http_stats",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.body_factory.enable_logging",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.body_factory.response_suppression_mode",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.connections_throttle",
+		"secure": false,
+		"value": "INT 500000"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.defer_accept",
+		"secure": false,
+		"value": "INT 45"
+	},
+	{
+		"configFile": "records.config",
+		"name": "LOCAL proxy.local.cluster.type",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.cluster_port",
+		"secure": false,
+		"value": "INT 8086"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.rsport",
+		"secure": false,
+		"value": "INT 8088"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.mcport",
+		"secure": false,
+		"value": "INT 8089"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.mc_group_addr",
+		"secure": false,
+		"value": "STRING 224.0.1.37"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.mc_ttl",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.log_bogus_mc_msgs",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.ethernet_interface",
+		"secure": false,
+		"value": "STRING lo"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.permit.pinning",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.ram_cache.algorithm",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.ram_cache.use_seen_filter",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.ram_cache.compress",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.limits.http.max_alts",
+		"secure": false,
+		"value": "INT 5"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.max_doc_size",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.mutex_retry_delay",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.search_default_domains",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.splitDNS.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.max_dns_in_flight",
+		"secure": false,
+		"value": "INT 2048"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.url_expansions",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.round_robin_nameservers",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.nameservers",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.resolv_conf",
+		"secure": false,
+		"value": "STRING /etc/resolv.conf"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.validate_query_name",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.timeout",
+		"secure": false,
+		"value": "INT 1440"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.strict_round_robin",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.logging_enabled",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.max_secs_per_buffer",
+		"secure": false,
+		"value": "INT 5"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.max_space_mb_for_logs",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.max_space_mb_for_orphan_logs",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.max_space_mb_headroom",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.hostname",
+		"secure": false,
+		"value": "STRING localhost"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.logfile_dir",
+		"secure": false,
+		"value": "STRING /var/log/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.logfile_perm",
+		"secure": false,
+		"value": "STRING rw-r--r--"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.custom_logs_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.squid_log_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.common_log_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.extended_log_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.separate_icp_logs",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.separate_host_logs",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "LOCAL proxy.local.log.collation_mode",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_host",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_port",
+		"secure": false,
+		"value": "INT 8085"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_secret",
+		"secure": false,
+		"value": "STRING foobar"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_host_tagged",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.collation_retry_sec",
+		"secure": false,
+		"value": "INT 5"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.rolling_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.rolling_interval_sec",
+		"secure": false,
+		"value": "INT 86400"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.rolling_offset_hr",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.rolling_size_mb",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.auto_delete_rolled_files",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.sampling_frequency",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.reverse_proxy.enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.header.parse.no_host_url_redirect",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.default_to_server_pac",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.default_to_server_pac_port",
+		"secure": false,
+		"value": "INT -1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.pristine_host_hdr",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.number.threads",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.SSLv2",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.TLSv1",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.compression",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.certification_level",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.cert_chain.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.CA.cert.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.verify.server",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.cert.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.private_key.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.CA.cert.filename",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.icp_interface",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.icp_port",
+		"secure": false,
+		"value": "INT 3130"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.multicast_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.icp.query_timeout",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.force",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.retry_count",
+		"secure": false,
+		"value": "INT 10"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.retry_interval",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.update.concurrent_updates",
+		"secure": false,
+		"value": "INT 100"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.sock_send_buffer_size_in",
+		"secure": false,
+		"value": "INT 262144"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.sock_recv_buffer_size_in",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.sock_send_buffer_size_out",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.sock_recv_buffer_size_out",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.core_limit",
+		"secure": false,
+		"value": "INT -1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.debug.enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.debug.tags",
+		"secure": false,
+		"value": "STRING http|dns"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dump_mem_info_frequency",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.slow.log.threshold",
+		"secure": false,
+		"value": "INT 10000"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.task_threads",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "cache.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "hosting.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "parent.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "plugin.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "records.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "remap.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "storage.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "volume.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver/"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.remap_required",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.file",
+		"secure": false,
+		"value": "STRING parent.config"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.url_remap.filename",
+		"secure": false,
+		"value": "STRING remap.config"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cluster.cluster_configuration ",
+		"secure": false,
+		"value": "STRING cluster.config"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.threshold.loadavg",
+		"secure": false,
+		"value": "25.0"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.threshold.availableBandwidthInKbps",
+		"secure": false,
+		"value": ">1750000"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "history.count",
+		"secure": false,
+		"value": "30"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.control.filename",
+		"secure": false,
+		"value": "STRING cache.config"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.RollingEnabled",
+		"secure": false,
+		"value": "3"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.RollingIntervalSec",
+		"secure": false,
+		"value": "86400"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.RollingOffsetHr",
+		"secure": false,
+		"value": "11"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.RollingSizeMb",
+		"secure": false,
+		"value": "4"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "location",
+		"secure": false,
+		"value": "/opt/trafficserver/etc/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.xml_config_file",
+		"name": "CONFIG proxy.config.log.xml_configFile",
+		"secure": false,
+		"value": "STRING logs_xml.config"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.threshold.queryTime",
+		"secure": false,
+		"value": "1000"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.polling.url",
+		"secure": false,
+		"value": "http://${hostname}/_astats?application=&inf.name=${interface_name}"
+	},
+	{
+		"configFile": "storage.config",
+		"name": "Disk_Volume",
+		"secure": false,
+		"value": "1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.hosting_filename",
+		"secure": false,
+		"value": "STRING hosting.config"
+	},
+	{
+		"configFile": "rascal.properties",
+		"name": "health.connection.timeout",
+		"secure": false,
+		"value": "2000"
+	},
+	{
+		"configFile": "12M_facts",
+		"name": "location",
+		"secure": false,
+		"value": "/opt/ort"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_accept_encoding_mismatch",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "chkconfig",
+		"name": "trafficserver",
+		"secure": false,
+		"value": "0:off\t1:off\t2:on\t3:on\t4:on\t5:on\t6:off"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.debug_filter",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.enable_reclaim",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.max_overage",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.show_location",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.allow_empty_doc",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "LOCAL proxy.config.cache.interim.storage",
+		"secure": false,
+		"value": "STRING NULL"
+	},
+	{
+		"configFile": "plugin.config",
+		"name": "regex_revalidate.so",
+		"secure": false,
+		"value": "--config regex_revalidate.config"
+	},
+	{
+		"configFile": "regex_revalidate.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.stack_dump_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.ttl_mode",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.dns.lookup_timeout",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.serve_stale_for",
+		"secure": false,
+		"value": "INT 6"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.enable_read_while_writer",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.background_fill_active_timeout",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.background_fill_completed_threshold",
+		"secure": false,
+		"value": "FLOAT 0.0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.log.extended2_log_enabled",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.exec_thread.affinity",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.exec_thread.autoconfig",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.exec_thread.limit",
+		"secure": false,
+		"value": "INT 8"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.thread_freelist_size",
+		"secure": false,
+		"value": "INT 1024"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.mlock_enabled",
+		"secure": false,
+		"value": "INT 2"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogFormat.Format",
+		"secure": false,
+		"value": "%<cqtq> chi=%<chi> phn=%<phn> php=%<php> shn=%<shn> url=%<cquuc> cqhm=%<cqhm> cqhv=%<cqhv> pssc=%<pssc> ttms=%<ttms> b=%<pscl> sssc=%<sssc> sscl=%<sscl> cfsc=%<cfsc> pfsc=%<pfsc> crc=%<crc> phr=%<phr> pqsn=%<pqsn> uas=\"%<{User-Agent}cqh>\" "
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogFormat.Name",
+		"secure": false,
+		"value": "custom_ats_2"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.Format",
+		"secure": false,
+		"value": "custom_ats_2"
+	},
+	{
+		"configFile": "logs_xml.config",
+		"name": "LogObject.Filename",
+		"secure": false,
+		"value": "custom_ats_2"
+	},
+	{
+		"configFile": "plugin.config",
+		"name": "astats_over_http.so",
+		"secure": false,
+		"value": ""
+	},
+	{
+		"configFile": "astats.config",
+		"name": "allow_ip",
+		"secure": false,
+		"value": "127.0.0.1,10.0.0.0/8"
+	},
+	{
+		"configFile": "astats.config",
+		"name": "allow_ip6",
+		"secure": false,
+		"value": "::1/128"
+	},
+	{
+		"configFile": "astats.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver"
+	},
+	{
+		"configFile": "astats.config",
+		"name": "path",
+		"secure": false,
+		"value": "_astats"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.http.compatibility.4-2-0-fixup",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.cipher_suite",
+		"secure": false,
+		"value": "STRING ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2:@STRENGTH"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.honor_cipher_order",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.cert.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.private_key.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.CA.cert.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.private_key.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.client.cert.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.CA.cert.path",
+		"secure": false,
+		"value": "STRING etc/trafficserver/ssl"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.SSLv3",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.ssl.server.multicert.filename",
+		"secure": false,
+		"value": "STRING ssl_multicert.config"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.negative_caching_enabled",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "astats.config",
+		"name": "record_types",
+		"secure": false,
+		"value": "122"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.transaction_active_timeout_in",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.body_factory.enable_customizations",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.negative_caching_lifetime",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.body_factory.template_sets_dir",
+		"secure": false,
+		"value": "STRING /etc/trafficserver/body_factory"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.ip_allow.filename",
+		"secure": false,
+		"value": "STRING ip_allow.config"
+	},
+	{
+		"configFile": "parent.config",
+		"name": "weight",
+		"secure": false,
+		"value": "1.2"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.fail_threshold",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.connect_attempts_timeout",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.per_parent_connect_attempts",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.total_connect_attempts",
+		"secure": false,
+		"value": "INT 3"
+	},
+	{
+		"configFile": "storage.config",
+		"name": "Drive_Letters",
+		"secure": false,
+		"value": "cache"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.cache.hostdb.sync_frequency",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.storage_size",
+		"secure": false,
+		"value": "INT 2048"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.hostdb.size",
+		"secure": false,
+		"value": "INT 2048"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.guaranteed_max_lifetime",
+		"secure": false,
+		"value": "INT 2592000"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.cache.ignore_client_cc_max_age",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.net.default_inactivity_timeout",
+		"secure": false,
+		"value": "INT 180"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.http.parent_proxy.mark_down_hostdb",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.allocator.dontdump_iobuffers",
+		"secure": false,
+		"value": "INT 0"
+	},
+	{
+		"configFile": "ip_allow.config",
+		"name": "location",
+		"secure": false,
+		"value": "/etc/trafficserver"
+	},
+	{
+		"configFile": "storage.config",
+		"name": "Drive_Prefix",
+		"secure": false,
+		"value": "/var/trafficserver"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.logfile.rolling_size_mb",
+		"secure": false,
+		"value": "INT 1"
+	},
+	{
+		"configFile": "records.config",
+		"name": "CONFIG proxy.config.diags.logfile.rolling_enabled",
+		"secure": false,
+		"value": "INT 2"
+	}
+]
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/030-BIND_ALL.json b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/030-BIND_ALL.json
new file mode 100644
index 000000000..9555b1b16
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/030-BIND_ALL.json
@@ -0,0 +1,7 @@
+{
+  "cdnName": "ALL",
+  "description": "Bind DNS Server V9 Profile for all CDNs",
+  "name": "BIND_ALL",
+  "routingDisabled": false,
+  "type": "UNK_PROFILE"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/040-CCR_CIAB.json b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/040-CCR_CIAB.json
new file mode 100644
index 000000000..1eb77bcf0
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/040-CCR_CIAB.json
@@ -0,0 +1,179 @@
+{
+  "cdnName": "CDN-in-a-Box",
+  "description": "Traffic Router for CDN-In-A-Box",
+  "name": "CCR_CIAB",
+  "routingDisabled": false,
+  "type": "TR_PROFILE",
+  "params": [
+    {
+      "configFile": "inputs.conf",
+      "name": "monitor:///opt/tomcat/logs/access.log",
+      "value": "index=index_odol_test;sourcetype=access_ccr"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "geolocation.polling.url",
+      "value": "https://trafficops.infra.ciab.test:443/GeoLite2-City.mmdb.gz"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "geolocation.polling.interval",
+      "value": "86400000"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "coveragezone.polling.interval",
+      "value": "3600000"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "coveragezone.polling.url",
+      "value": "https://trafficops.infra.ciab.test:443/coverage-zone.json"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "domain_name",
+      "value": "mycdn.ciab.test"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.ttls.AAAA",
+      "value": "3600"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.ttls.A",
+      "value": "3600"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.soa.expire",
+      "value": "604800"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.soa.minimum",
+      "value": "30"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.soa.admin",
+      "value": "twelve_monkeys"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.soa.retry",
+      "value": "7200"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.soa.refresh",
+      "value": "28800"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.ttls.NS",
+      "value": "3600"
+    },
+    {
+      "configFile": "server.xml",
+      "name": "api.port",
+      "value": "3333"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "api.cache-control.max-age",
+      "value": "10"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "federationmapping.polling.url",
+      "value": "https://${toHostname}/internal/api/1.3/federations.json"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "federationmapping.polling.interval",
+      "value": "60000"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.ttls.DNSKEY",
+      "value": "30"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.ttls.DS",
+      "value": "30"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "zonemanager.cache.maintenance.interval",
+      "value": "300"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "keystore.maintenance.interval",
+      "value": "300"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "dnssec.dynamic.response.expiration",
+      "value": "300s"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "zonemanager.threadpool.scale",
+      "value": "0.50"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "consistent.dns.routing",
+      "value": "true"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "neustar.polling.url",
+      "value": "https://trafficops.infra.ciab.test:443/neustar.tar.gz"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "neustar.polling.interval",
+      "value": "86400000"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "certificates.polling.interval",
+      "value": "300000"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "steeringmapping.polling.interval",
+      "value": "60000"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "tld.ttls.SOA",
+      "value": "86400"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "edge.dns.limit",
+      "value": "6"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "edge.http.limit",
+      "value": "6"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "edge.dns.routing",
+      "value": "true"
+    },
+    {
+      "configFile": "CRConfig.json",
+      "name": "edge.http.routing",
+      "value": "true"
+    }
+  ]
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/050-ENROLLER_ALL.json b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/050-ENROLLER_ALL.json
new file mode 100644
index 000000000..9eda0c7fe
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/050-ENROLLER_ALL.json
@@ -0,0 +1,7 @@
+{
+  "cdnName": "ALL",
+  "description": "Enroller Service Profile for all CDNs",
+  "name": "ENROLLER_ALL",
+  "routingDisabled": false,
+  "type": "UNK_PROFILE"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/060-GLOBAL.json b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/060-GLOBAL.json
new file mode 100644
index 000000000..cfc309f21
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/060-GLOBAL.json
@@ -0,0 +1,48 @@
+{
+  "cdnName": "ALL",
+  "name": "GLOBAL",
+  "type": "UNK_PROFILE",
+  "description": "Global Traffic Ops profile, DO NOT DELETE",
+  "params": [
+    {
+      "configFile": "global",
+      "name": "tm.url",
+      "value": "https://trafficops.infra.ciab.test:443/"
+    },
+    {
+      "configFile": "global",
+      "name": "tm.logourl",
+      "value": "/images/tc_logo.png"
+    },
+    {
+      "configFile": "global",
+      "name": "tm.instance_name",
+      "value": "CDN-In-A-Box"
+    },
+    {
+      "configFile": "global",
+      "name": "tm.toolname",
+      "value": "Traffic Ops"
+    },
+    {
+      "configFile": "global",
+      "name": "use_reval_pending",
+      "value": "0"
+    },
+    {
+      "configFile": "global",
+      "name": "use_tenancy",
+      "value": "1"
+    },
+    {
+      "configFile": "global",
+      "name": "default_geo_miss_latitude",
+      "value": "0"
+    },
+    {
+      "configFile": "global",
+      "name": "default_geo_miss_longitude",
+      "value": "-1"
+    }
+  ]
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/070-RASCAL-Traffic_Monitor.json b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/070-RASCAL-Traffic_Monitor.json
new file mode 100644
index 000000000..0fc548355
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/profiles/070-RASCAL-Traffic_Monitor.json
@@ -0,0 +1,68 @@
+{
+  "cdnName": "CDN-in-a-Box",
+  "description": "Traffic Monitor for CDN-in-a-Box",
+  "name": "RASCAL-Traffic_Monitor",
+  "type": "TM_PROFILE",
+  "params": [
+    {
+      "configFile": "rascal-config.txt",
+      "name": "hack.ttl",
+      "value": "30"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "health.event-count",
+      "value": "200"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "health.polling.interval",
+      "value": "6000"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "health.threadPool",
+      "value": "4"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "health.timepad",
+      "value": "0"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "heartbeat.polling.interval",
+      "value": "3000"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "location",
+      "value": "/opt/traffic_monitor/conf"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "peers.polling.interval",
+      "value": "3000"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "tm.crConfig.polling.url",
+      "value": "https://${tmHostname}/CRConfig-Snapshots/${cdnName}/CRConfig.xml"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "tm.dataServer.polling.url",
+      "value": "https://${tmHostname}/dataserver/orderby/id"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "tm.healthParams.polling.url",
+      "value": "https://${tmHostname}/health/${cdnName}"
+    },
+    {
+      "configFile": "rascal-config.txt",
+      "name": "tm.polling.interval",
+      "value": "2000"
+    }
+  ]
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/regions/dc.json b/infrastructure/cdn-in-a-box/traffic_ops_data/regions/010-dc.json
similarity index 56%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/regions/dc.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/regions/010-dc.json
index 850d6cbaa..b579d7ffc 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/regions/dc.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/regions/010-dc.json
@@ -1,4 +1,4 @@
 {
-  "division": 2,
+  "divisionName": "USA",
   "name": "Washington, D.C"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/regions/020-montreal.json b/infrastructure/cdn-in-a-box/traffic_ops_data/regions/020-montreal.json
new file mode 100644
index 000000000..78be5e7fd
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/regions/020-montreal.json
@@ -0,0 +1,4 @@
+{
+  "divisionName": "Quebec",
+  "name": "Montreal"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/regions/montreal.json b/infrastructure/cdn-in-a-box/traffic_ops_data/regions/montreal.json
deleted file mode 100644
index e28638182..000000000
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/regions/montreal.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "division": 1,
-  "name": "Montreal"
-}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/servers/010-bind_server.json b/infrastructure/cdn-in-a-box/traffic_ops_data/servers/010-bind_server.json
new file mode 100644
index 000000000..cf7fe7d7a
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/servers/010-bind_server.json
@@ -0,0 +1,20 @@
+{
+  "hostName": "dns",
+  "domainName": "infra.ciab.test",
+  "cachegroup": "CDN_in_a_Box_Edge",
+  "interfaceName": "eth0",
+  "ipAddress": "172.16.239.254",
+  "ipNetmask": "255.255.255.0",
+  "ipGateway": "172.16.239.1",
+  "ip6Address": "fc01:9400:1000:8::254",
+  "ip6Gateway": "fc01:9400:1000:8::1",
+  "interfaceMtu": 1500,
+  "type": "BIND",
+  "physLocation": "Apachecon North America 2018",
+  "profile": "BIND_ALL",
+  "cdnName": "ALL",
+  "updPending": false,
+  "status": "ONLINE",
+  "tcpPort": 53,
+  "httpsPort": 443
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/servers/020-db_server.json b/infrastructure/cdn-in-a-box/traffic_ops_data/servers/020-db_server.json
new file mode 100644
index 000000000..66afe75f1
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/servers/020-db_server.json
@@ -0,0 +1,21 @@
+{
+  "hostName": "db",
+  "domainName": "infra.ciab.test",
+  "cachegroup": "CDN_in_a_Box_Edge",
+  "interfaceName": "eth0",
+  "ipAddress": "172.16.239.10",
+  "ipNetmask": "255.255.255.0",
+  "ipGateway": "172.16.239.1",
+  "ip6Address": "fc01:9400:1000:8::10",
+  "ip6Gateway": "fc01:9400:1000:8::1",
+  "interfaceMtu": 1500,
+  "type": "TRAFFIC_OPS_DB",
+  "physLocation": "Apachecon North America 2018",
+  "profile": "TRAFFIC_OPS_DB",
+  "cdnName": "ALL",
+  "updPending": false,
+  "status": "ONLINE",
+  "tcpPort": 5432,
+  "httpsPort": 5432
+}
+
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/servers/030-enroller_server.json b/infrastructure/cdn-in-a-box/traffic_ops_data/servers/030-enroller_server.json
new file mode 100644
index 000000000..ea1be9558
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/servers/030-enroller_server.json
@@ -0,0 +1,20 @@
+{
+  "hostName": "enroller",
+  "domainName": "infra.ciab.test",
+  "cachegroup": "CDN_in_a_Box_Edge",
+  "interfaceName": "eth0",
+  "ipAddress": "172.16.239.200",
+  "ipNetmask": "255.255.255.0",
+  "ipGateway": "172.16.239.1",
+  "ip6Address": "fc01:9400:1000:8::200",
+  "ip6Gateway": "fc01:9400:1000:8::1",
+  "interfaceMtu": 1500,
+  "type": "ENROLLER",
+  "physLocation": "Apachecon North America 2018",
+  "profile": "ENROLLER_ALL",
+  "cdnName": "ALL",
+  "updPending": false,
+  "status": "ONLINE",
+  "tcpPort": 53,
+  "httpsPort": 443
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/servers/040-trafficvault_server.json b/infrastructure/cdn-in-a-box/traffic_ops_data/servers/040-trafficvault_server.json
new file mode 100644
index 000000000..3a5e987a9
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/servers/040-trafficvault_server.json
@@ -0,0 +1,20 @@
+{
+  "hostName": "trafficvault",
+  "domainName": "infra.ciab.test",
+  "cachegroup": "CDN_in_a_Box_Edge",
+  "interfaceName": "eth0",
+  "ipAddress": "172.16.239.50",
+  "ipNetmask": "255.255.255.0",
+  "ipGateway": "172.16.239.1",
+  "ip6Address": "fc01:9400:1000:8::50",
+  "ip6Gateway": "fc01:9400:1000:8::1",
+  "interfaceMtu": 1500,
+  "type": "RIAK",
+  "physLocation": "Apachecon North America 2018",
+  "profile": "RIAK_ALL",
+  "cdnName": "ALL",
+  "updPending": false,
+  "status": "ONLINE",
+  "tcpPort": 8088,
+  "httpsPort": 8088 
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/010-tenant01.json b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/010-tenant01.json
new file mode 100644
index 000000000..d1b0f52a4
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/010-tenant01.json
@@ -0,0 +1,5 @@
+{
+    "name": "tenant01",
+    "active": true,
+    "parentName": "root"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/020-tenant02.json b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/020-tenant02.json
new file mode 100644
index 000000000..9047fddcf
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/020-tenant02.json
@@ -0,0 +1,5 @@
+{
+    "name": "tenant02",
+    "active": true,
+    "parentName": "root"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant11.json b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/030-tenant11.json
similarity index 62%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant11.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/tenants/030-tenant11.json
index 0903a35c1..c350a8c4d 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant11.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/030-tenant11.json
@@ -1,5 +1,5 @@
 {
     "name": "tenant11",
     "active": true,
-    "parentId": 2
+    "parentName": "tenant01"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant12.json b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/040-tenant12.json
similarity index 62%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant12.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/tenants/040-tenant12.json
index 33ab974bc..a5522cfbd 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant12.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/040-tenant12.json
@@ -1,5 +1,5 @@
 {
     "name": "tenant12",
     "active": true,
-    "parentId": 2
+    "parentName": "tenant01"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant21.json b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/050-tenant21.json
similarity index 62%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant21.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/tenants/050-tenant21.json
index b5204a0ca..505a882bb 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant21.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/050-tenant21.json
@@ -1,5 +1,5 @@
 {
     "name": "tenant21",
     "active": true,
-    "parentId": 5
+    "parentName": "tenant02"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant22.json b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/060-tenant22.json
similarity index 62%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant22.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/tenants/060-tenant22.json
index 67e58c1df..c2561a6df 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant22.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/060-tenant22.json
@@ -1,5 +1,5 @@
 {
     "name": "tenant22",
     "active": true,
-    "parentId": 5
+    "parentName": "tenant02"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant1.json b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant1.json
deleted file mode 100644
index 264904dbf..000000000
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant1.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "name": "tenant1",
-    "active": true,
-    "parentId": 1
-}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant2.json b/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant2.json
deleted file mode 100644
index 951f6ecd9..000000000
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/tenants/tenant2.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-    "name": "tenant2",
-    "active": true,
-    "parentId": 1
-}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/types/010-BIND.json b/infrastructure/cdn-in-a-box/traffic_ops_data/types/010-BIND.json
new file mode 100644
index 000000000..2e14a968c
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/types/010-BIND.json
@@ -0,0 +1,5 @@
+{
+  "description": "Bind V9 DNS Server for ALL CDNs",
+  "name": "BIND",
+  "useInTable": "server"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/types/020-ENROLLER.json b/infrastructure/cdn-in-a-box/traffic_ops_data/types/020-ENROLLER.json
new file mode 100644
index 000000000..bfe9e523f
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/types/020-ENROLLER.json
@@ -0,0 +1,5 @@
+{
+  "description": "Enroller Service",
+  "name": "ENROLLER",
+  "useInTable": "server"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/types/030-TC_LOC.json b/infrastructure/cdn-in-a-box/traffic_ops_data/types/030-TC_LOC.json
new file mode 100644
index 000000000..ae54bb101
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/types/030-TC_LOC.json
@@ -0,0 +1,5 @@
+{
+  "description": "Location for Traffic Control Component Servers",
+  "name": "TC_LOC",
+  "useInTable": "cachegroup"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/types/040-TR_LOC.json b/infrastructure/cdn-in-a-box/traffic_ops_data/types/040-TR_LOC.json
new file mode 100644
index 000000000..5318c65bb
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/types/040-TR_LOC.json
@@ -0,0 +1,5 @@
+{
+  "description": "Traffic Router Logical Site",
+  "name": "TR_LOC",
+  "useInTable": "cachegroup"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/users/tmonitor.json b/infrastructure/cdn-in-a-box/traffic_ops_data/users/010-tmonitor.json
similarity index 76%
rename from infrastructure/cdn-in-a-box/traffic_ops_data/users/tmonitor.json
rename to infrastructure/cdn-in-a-box/traffic_ops_data/users/010-tmonitor.json
index 85062b923..5e0791f5e 100644
--- a/infrastructure/cdn-in-a-box/traffic_ops_data/users/tmonitor.json
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/users/010-tmonitor.json
@@ -3,7 +3,7 @@
   "fullName": "Traffic Monitor",
   "localPasswd": "$TM_PASSWORD",
   "confirmLocalPasswd": "$TM_PASSWORD",
-  "role": 1,
-  "tenantId": 1,
+  "rolename": "operations",
+  "tenant": "root",
   "username": "$TM_USER"
 }
diff --git a/infrastructure/cdn-in-a-box/traffic_ops_data/users/020-tvault.json b/infrastructure/cdn-in-a-box/traffic_ops_data/users/020-tvault.json
new file mode 100644
index 000000000..0f4672d02
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_ops_data/users/020-tvault.json
@@ -0,0 +1,9 @@
+{
+  "email": "$TV_EMAIL",
+  "fullName": "Traffic Vault",
+  "localPasswd": "$TV_PASSWORD",
+  "confirmLocalPasswd": "$TV_PASSWORD",
+  "rolename": "operations",
+  "tenant": "root",
+  "username": "$TV_USER"
+}
diff --git a/infrastructure/cdn-in-a-box/traffic_portal/Dockerfile b/infrastructure/cdn-in-a-box/traffic_portal/Dockerfile
index b6dbfb8d8..b2b14795e 100644
--- a/infrastructure/cdn-in-a-box/traffic_portal/Dockerfile
+++ b/infrastructure/cdn-in-a-box/traffic_portal/Dockerfile
@@ -25,7 +25,7 @@ FROM centos:7
 RUN curl -sL https://rpm.nodesource.com/setup_6.x | bash -
 
 # Override TRAFFIC_PORTAL_RPM arg to use a different one using --build-arg TRAFFIC_PORTAL_RPM=...  Can be local file or http://...
-ARG TRAFFIC_PORTAL_RPM=traffic_portal.rpm
+ARG TRAFFIC_PORTAL_RPM=traffic_portal/traffic_portal.rpm
 ADD $TRAFFIC_PORTAL_RPM /
 
 ARG TO_HOST=$TO_HOST
@@ -37,10 +37,16 @@ RUN yum install -y \
       jq \
       nodejs \
       openssl \
+      gettext \
+      bind-utils \
+      net-tools \
       /$(basename $TRAFFIC_PORTAL_RPM) && \
     rm /$(basename $TRAFFIC_PORTAL_RPM) && \
     yum clean all || \
     echo "ERROR INSTALLING PACKAGES"
 
-ADD run.sh /
+ADD enroller/server_template.json \
+    traffic_portal/run.sh \
+    traffic_ops/to-access.sh /
+
 CMD /run.sh
diff --git a/infrastructure/cdn-in-a-box/traffic_portal/run.sh b/infrastructure/cdn-in-a-box/traffic_portal/run.sh
index 0bf2c87e9..453225c5b 100755
--- a/infrastructure/cdn-in-a-box/traffic_portal/run.sh
+++ b/infrastructure/cdn-in-a-box/traffic_portal/run.sh
@@ -27,28 +27,44 @@ LOGFILE="/var/log/traffic_portal/traffic_portal.log"
 MIN_UPTIME="5000"
 SPIN_SLEEP_TIME="2000"
 
+source /to-access.sh
 
-key=/etc/pki/tls/private/localhost.key
-cert=/etc/pki/tls/certs/localhost.crt
-ca=/etc/pki/tls/certs/ca-bundle.crt
-openssl req -newkey rsa:2048 -nodes -keyout $key -x509 -days 365 -out $cert -subj "/C=$CERT_COUNTRY/ST=$CERT_STATE/L=$CERT_CITY/O=$CERT_COMPANY"
+# Wait on SSL certificate generation
+until [ -f "$X509_CA_DONE_FILE" ] 
+do
+  echo "Waiting on Shared SSL certificate generation"
+  sleep 3
+done
+
+# Source the CIAB-CA shared SSL environment
+source $X509_CA_ENV_FILE
 
+# Trust the CIAB-CA at the System level
+cp $X509_CA_CERT_FILE /etc/pki/ca-trust/source/anchors
+update-ca-trust extract
+
+# Configuration of Traffic Portal
+key=$X509_INFRA_KEY_FILE
+cert=$X509_INFRA_CERT_FILE
+ca=/etc/pki/tls/certs/ca-bundle.crt
 
-# set configs to point to TO_HOST
-sed -i -e "/^\s*base_url:/ s@'.*'@'https://$TO_HOST:6443/api/'@" /etc/traffic_portal/conf/config.js
+# set configs to point to TO_FQDN
+sed -i -e "/^\s*base_url:/ s@'.*'@'https://$TO_FQDN:$TO_PORT/api/'@" /etc/traffic_portal/conf/config.js
+sed -i -e "/^\s*cert:/ s@'.*'@'$cert'@" /etc/traffic_portal/conf/config.js
+sed -i -e "/^\s*key:/ s@'.*'@'$key'@" /etc/traffic_portal/conf/config.js
 
 props=/opt/traffic_portal/public/traffic_portal_properties.json
 tmp=$(mktemp)
 
 echo "TO_HOST: $TO_HOST"
+echo "TO_HOST: $TO_PORT"
+echo "TO_FQDN: $TO_FQDN"
 
-jq --arg TO_HOST $TO_HOST '.properties.api.baseUrl = "https://"+$TO_HOST' <$props >$tmp
+jq --arg TO_FQDN "$TO_FQDN:$TO_PORT" '.properties.api.baseUrl = "https://"+$TO_FQDN' <$props >$tmp
 mv $tmp $props
 
-export TO_USER=$TO_ADMIN_USER
-export TO_PASSWORD=$TO_ADMIN_PASSWORD
-. /to-access.sh
-to-enroll $(hostname -s)
+# Enroll the Traffic Portal
+to-enroll "tp" ALL || (while true; do echo "enroll failed."; sleep 3 ; done)
 
 # Add node to the path for situations in which the environment is passed.
 PATH=$FOREVER_BIN_DIR:$NODE_BIN_DIR:$PATH
diff --git a/infrastructure/cdn-in-a-box/traffic_router/Dockerfile b/infrastructure/cdn-in-a-box/traffic_router/Dockerfile
new file mode 100644
index 000000000..c940165c3
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_router/Dockerfile
@@ -0,0 +1,61 @@
+# 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.
+############################################################
+# Dockerfile to build Traffic Router 3.0 
+# Based on CentOS 7.x
+############################################################
+
+FROM centos:7
+MAINTAINER Dan Kirkwood
+
+# Default values for TMCAT RPM and RPM -- override with `docker build --build-arg JDK=...'
+ARG JDK8_RPM=http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.rpm
+ARG TRAFFIC_ROUTER_RPM=traffic_router/traffic_router.rpm
+ARG TOMCAT_RPM=traffic_router/tomcat.rpm 
+
+### Common for all sub-component builds
+RUN	yum -y install epel-release git rpm-build net-tools iproute nc wget tar unzip \
+          perl-JSON perl-WWWCurl which make autoconf automake gcc gcc-c++ apr apr-devel \
+					openssl openssl-devel bind-utils net-tools perl-JSON-PP gettext && \ 
+    yum -y clean all
+
+ADD $TRAFFIC_ROUTER_RPM /traffic_router.rpm
+ADD $TOMCAT_RPM /tomcat.rpm
+
+## Install and link java_home to /opt/java
+RUN curl -kvsL -o /jdk8.rpm -H  'Cookie: oraclelicense=accept-securebackup-cookie;' "$JDK8_RPM"  && \
+    rpm -ivh /jdk8.rpm && \
+    ln -sfv $(find /usr/java -mindepth 1 -maxdepth 1 -type d -name jdk\*)   /opt/java
+
+# Install Tomcat and Tomcat Native 
+RUN cd / && rpm2cpio /traffic_router.rpm | cpio -ivd && \
+    rpm2cpio /tomcat.rpm | cpio -ivd && \ 
+ 		tar -C /opt -xvpf /opt/tomcat/bin/tomcat-native.tar.gz
+
+# Compile and install Tomcat-native
+RUN	cd $(find /opt -maxdepth 1 -type d | grep tomcat-native )/native && \
+    ./configure --prefix=/opt/tomcat --with-apr=`which apr-1-config` --with-java-home=/opt/java --with-ssl && \
+		make install
+     
+ADD enroller/server_template.json \ 
+    traffic_router/run.sh \ 
+    traffic_ops/to-access.sh \ 
+    /
+
+EXPOSE 53 80 3333
+
+CMD /run.sh
diff --git a/infrastructure/cdn-in-a-box/traffic_router/run.sh b/infrastructure/cdn-in-a-box/traffic_router/run.sh
new file mode 100755
index 000000000..fbc41f5fc
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_router/run.sh
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+# 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.
+NAME="Traffic Router Application"
+
+# Global Vars for FQDNs, ports, etc
+source /to-access.sh
+
+CATALINA_HOME="/opt/tomcat"
+CATALINA_BASE="/opt/traffic_router"
+CATALINA_OUT="$CATALINA_HOME/logs/catalina.log"
+CATALINA_LOG="$CATALINA_HOME/logs/catalina.$(date +%Y-%m-%d).log"
+CATALINA_PID="$CATALINA_BASE/temp/tomcat.pid"
+
+CATALINA_OPTS="\
+  -server -Xms2g -Xmx8g \
+  -Djava.library.path=$CATALINA_HOME/lib \
+  -Dlog4j.configuration=file://$CATALINA_BASE/conf/log4j.properties \
+  -Dorg.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER=false \
+  -XX:+UseG1GC \
+  -XX:+UnlockExperimentalVMOptions \
+  -XX:InitiatingHeapOccupancyPercent=30"
+
+JAVA_HOME=/opt/java
+JAVA_OPTS="\
+  -Dcache.config.json.refresh.period=5000 \
+  -Djava.awt.headless=true \
+  -Djava.security.egd=file:/dev/./urandom"
+
+TO_PROPERTIES="$CATALINA_BASE/conf/traffic_ops.properties"
+TM_PROPERTIES="$CATALINA_BASE/conf/traffic_monitor.properties"
+LOGFILE="$CATALINA_BASE/var/log/traffic_router.log"
+ACCESSLOG="$CATALINA_BASE/var/log/access.log"
+
+export JAVA_HOME JAVA_OPTS
+export TO_PROPERTIES TM_PROPERTIES 
+export CATALINA_HOME CATALINA_BASE CATALINA_OPTS CATALINA_OUT CATALINA_PID
+
+# Wait on SSL certificate generation
+until [ -f "$X509_CA_DONE_FILE" ] 
+do
+  echo "Waiting on Shared SSL certificate generation"
+  sleep 3
+done
+
+# Source the CIAB-CA shared SSL environment
+source $X509_CA_ENV_FILE
+
+# Copy the CIAB-CA certificate to the traffic_router conf so it can be added to the trust store
+cp $X509_CA_CERT_FILE $CATALINA_BASE/conf
+cp $X509_CA_CERT_FILE /etc/pki/ca-trust/source/anchors
+update-ca-trust extract
+
+# Enroll Traffic Router
+to-enroll tr || (while true; do echo "enroll failed."; sleep 3 ; done)
+
+# Add traffic 
+for crtfile in $(find $CATALINA_BASE/conf -name \*.crt -type f) 
+do 
+  alias=$(echo $crtfile |sed -e 's/.crt//g' |tr [:upper:] [:lower:]); 
+  cacerts=$(find $JAVA_HOME -follow -name cacerts); echo $cacerts; 
+  keytool=$JAVA_HOME/bin/keytool;  
+   
+  $keytool -list -alias $alias -keystore $cacerts -storepass changeit -noprompt > /dev/null;    
+
+  if [ $? -ne 0 ]; then     
+     echo "Installing certificate ${crtfile}..";     
+     $keytool -import -trustcacerts -file $crtfile -alias $alias -keystore $cacerts -storepass changeit -noprompt;   
+  fi; 
+done
+
+# Configure TO properties
+# File: /opt/traffic_router/conf/traffic_ops.properties
+echo "" > $TO_PROPERTIES
+echo "traffic_ops.username=$TO_ADMIN_USER" >> $TO_PROPERTIES
+echo "traffic_ops.password=$TO_ADMIN_PASSWORD" >> $TO_PROPERTIES
+
+# Configure TM properties
+# File: /opt/traffic_router/conf/traffic_monitor.properties
+echo "traffic_monitor.bootstrap.hosts=$TM_FQDN:$TM_PORT;" >> $TM_PROPERTIES
+echo "traffic_monitor.properties.reload.period=60000" >> $TM_PROPERTIES
+
+# Wait for traffic monitor
+until nc $TM_FQDN $TM_PORT </dev/null >/dev/null 2>&1; do
+  echo "Waiting for Traffic Monitor to start..."
+  sleep 3
+done
+
+touch $LOGFILE $ACCESSLOG
+tail -F $CATALINA_OUT $CATALINA_LOG $LOGFILE $ACCESSLOG &  
+
+exec /opt/tomcat/bin/catalina.sh run 
diff --git a/infrastructure/cdn-in-a-box/traffic_vault/Dockerfile b/infrastructure/cdn-in-a-box/traffic_vault/Dockerfile
new file mode 100644
index 000000000..107040c13
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_vault/Dockerfile
@@ -0,0 +1,37 @@
+# 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.
+FROM basho/riak-kv:ubuntu-2.2.3
+
+EXPOSE 8087 8098
+
+RUN rm -rfv /etc/riak/prestart.d/* /etc/riak/poststart.d/*
+
+RUN echo 'APT::Install-Recommends 0;' >> /etc/apt/apt.conf.d/01norecommends \
+ && echo 'APT::Install-Suggests 0;' >> /etc/apt/apt.conf.d/01norecommends \
+ && apt-get update \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y vim.tiny wget net-tools sudo net-tools ca-certificates unzip dnsutils \
+ && rm -rf /var/lib/apt/lists/* && rm -rf /etc/apt/apt.conf.d/docker-gzip-indexes
+
+ADD traffic_vault/prestart.d/* /etc/riak/prestart.d/
+ADD traffic_vault/poststart.d/* /etc/riak/poststart.d/
+ADD enroller/server_template.json \
+    traffic_vault/run.sh \
+    traffic_vault/sslkeys.xml \
+    traffic_ops/to-access.sh \
+    /
+
+CMD /run.sh
diff --git a/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/00-enable-security.sh b/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/00-enable-security.sh
new file mode 100644
index 000000000..7a5ac80b7
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/00-enable-security.sh
@@ -0,0 +1,22 @@
+#!/bin/bash 
+set -x 
+
+# Enable Security
+$RIAK_ADMIN security enable
+$RIAK_ADMIN security add-group admins
+$RIAK_ADMIN security add-group keysusers
+
+# Add users
+$RIAK_ADMIN security add-user "$TV_ADMIN_USER" password="$TV_ADMIN_PASSWORD" groups=admins
+$RIAK_ADMIN security add-user "$TV_RIAK_USER" password="$TV_RIAK_PASSWORD" groups=keysusers
+$RIAK_ADMIN security add-source "$TV_ADMIN_USER" 0.0.0.0/0 password
+$RIAK_ADMIN security add-source "$TV_RIAK_USER" 0.0.0.0/0 password
+
+# Grant privs to admins for everything
+$RIAK_ADMIN security grant riak_kv.list_buckets,riak_kv.list_keys,riak_kv.get,riak_kv.put,riak_kv.delete on any to admins
+
+# Grant privs to keysuser for ssl, dnssec, and url_sig_keys buckets only
+$RIAK_ADMIN security grant riak_kv.get,riak_kv.put,riak_kv.delete on default ssl to keysusers
+$RIAK_ADMIN security grant riak_kv.get,riak_kv.put,riak_kv.delete on default dnssec to keysusers
+$RIAK_ADMIN security grant riak_kv.get,riak_kv.put,riak_kv.delete on default url_sig_keys to keysusers
+$RIAK_ADMIN security grant riak_kv.get,riak_kv.put,riak_kv.delete on default cdn_uri_sig_keys  to keysusers
diff --git a/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/01-add-search-group.sh b/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/01-add-search-group.sh
new file mode 100644
index 000000000..ea75758f4
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/01-add-search-group.sh
@@ -0,0 +1,9 @@
+source /to-access.sh
+
+$RIAK_ADMIN security grant search.admin on schema to admin
+$RIAK_ADMIN security grant search.admin on index to admin
+$RIAK_ADMIN security grant search.query on index to admin
+$RIAK_ADMIN security grant search.query on index sslkeys to admin
+$RIAK_ADMIN security grant search.query on index to riakuser
+$RIAK_ADMIN security grant search.query on index sslkeys to riakuser
+$RIAK_ADMIN security grant riak_core.set_bucket on any to admin
diff --git a/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/02-add-search-schema.sh b/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/02-add-search-schema.sh
new file mode 100644
index 000000000..d4aa3864d
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_vault/poststart.d/02-add-search-schema.sh
@@ -0,0 +1,7 @@
+source /to-access.sh
+
+curl -kvs -XPUT -H 'Content-Type:application/xml' "https://$TV_ADMIN_USER:$TV_ADMIN_PASSWORD@$TV_FQDN:$TV_HTTPS_PORT/search/schema/sslkeys" -d @/sslkeys.xml 
+
+curl -kvs -XPUT -H 'Content-Type:application/json' "https://$TV_ADMIN_USER:$TV_ADMIN_PASSWORD@$TV_FQDN:$TV_HTTPS_PORT/search/index/sslkeys" -d '{"schema":"sslkeys"}'
+
+curl -kvs -XPUT -H 'Content-Type:application/json' "https://$TV_ADMIN_USER:$TV_ADMIN_PASSWORD@$TV_FQDN:$TV_HTTPS_PORT/buckets/ssl/props" -d'{"props":{"search_index":"sslkeys"}}'
diff --git a/infrastructure/cdn-in-a-box/traffic_vault/prestart.d/00-config.sh b/infrastructure/cdn-in-a-box/traffic_vault/prestart.d/00-config.sh
new file mode 100644
index 000000000..3dbf11db4
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_vault/prestart.d/00-config.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# Update system shared CA support
+source /to-access.sh
+
+# Wait on SSL certificate generation
+until [ -f "$X509_CA_DONE_FILE" ] 
+do
+     echo "Waiting on Shared SSL certificate generation"
+     sleep 3
+done
+
+# Source the CIAB-CA shared SSL environment
+source "$X509_CA_ENV_FILE"
+
+# Copy the CIAB-CA certificate to the traffic_router conf so it can be added to the trust store
+cp $X509_CA_CERT_FILE /usr/local/share/ca-certificates
+update-ca-certificates
+
+# Grep out the existing SSL and Socket listener config
+cp -af /etc/riak/riak.conf /etc/riak/riak.conf.orig
+grep -v -E '^(listener|#)' /etc/riak/riak.conf.orig  | uniq | sort > /etc/riak/riak.conf
+
+# Update the riak listener config
+echo "nodename = riak@0.0.0.0" >> /etc/riak.conf
+echo "listener.protobuf.internal = 0.0.0.0:$TV_INT_PORT" >> /etc/riak/riak.conf
+echo "listener.http.internal = 0.0.0.0:$TV_HTTP_PORT" >> /etc/riak/riak.conf
+echo "listener.https.internal = 0.0.0.0:$TV_HTTPS_PORT" >> /etc/riak/riak.conf
+
+# Update SSL/TLS Certificate Config
+echo "ssl.certfile = $X509_INFRA_CERT_FILE" >> /etc/riak/riak.conf
+echo "ssl.keyfile = $X509_INFRA_KEY_FILE" >> /etc/riak/riak.conf
+echo "ssl.cacertfile = /etc/pki/tls/certs/ca-bundle.crt" >> /etc/riak/riak.conf
+
+# Enable search with Apache Solr
+echo "search = on" >>  /etc/riak/riak.conf
diff --git a/infrastructure/cdn-in-a-box/traffic_vault/run.sh b/infrastructure/cdn-in-a-box/traffic_vault/run.sh
new file mode 100755
index 000000000..ed40e6b4c
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_vault/run.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+# 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.
+
+. /to-access.sh
+
+TO_URL=https://${TO_FQDN}:${TO_PORT}
+TO_USER=$TV_USER
+TO_PASSWORD=$TV_PASSWORD
+
+# TODO: Fix Traffic Vault Enrollment
+#to-enroll "tv" ALL || (while true; do echo "enroll failed."; sleep 3 ; done)
+
+${RIAK_HOME}/riak-cluster.sh
diff --git a/infrastructure/cdn-in-a-box/traffic_vault/sslkeys.xml b/infrastructure/cdn-in-a-box/traffic_vault/sslkeys.xml
new file mode 100644
index 000000000..245b8bc50
--- /dev/null
+++ b/infrastructure/cdn-in-a-box/traffic_vault/sslkeys.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+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.
+-->
+
+<schema name="schedule" version="1.5">
+ <fields>
+   <field name="cdn"   type="string"  indexed="true" stored="true" />
+   <field name="deliveryservice"   type="string"  indexed="true" stored="true" />
+   <field name="hostname"    type="string"     indexed="true" stored="true" />
+   <field name="certificate.crt" type="string" indexed="false" stored="true" />
+   <field name="certificate.key" type="string" indexed="false" stored="true" />
+
+   <!-- All of these fields are required by Riak Search -->
+   <field name="_yz_id"   type="_yz_str" indexed="true" stored="true"  multiValued="false" required="true"/>
+   <field name="_yz_ed"   type="_yz_str" indexed="true" stored="false" multiValued="false"/>
+   <field name="_yz_pn"   type="_yz_str" indexed="true" stored="false" multiValued="false"/>
+   <field name="_yz_fpn"  type="_yz_str" indexed="true" stored="false" multiValued="false"/>
+   <field name="_yz_vtag" type="_yz_str" indexed="true" stored="false" multiValued="false"/>
+   <field name="_yz_rk"   type="_yz_str" indexed="true" stored="true"  multiValued="false"/>
+   <field name="_yz_rt"   type="_yz_str" indexed="true" stored="true"  multiValued="false"/>
+   <field name="_yz_rb"   type="_yz_str" indexed="true" stored="true"  multiValued="false"/>
+   <field name="_yz_err"  type="_yz_str" indexed="true" stored="false" multiValued="false"/>
+
+   <!--catch all field-->
+   <dynamicField name="*" type="ignored"  />
+
+ </fields>
+
+ <uniqueKey>_yz_id</uniqueKey>
+
+ <types>
+    <fieldType name="string" class="solr.StrField" sortMissingLast="true" />
+    <!-- YZ String: Used for non-analyzed fields -->
+    <fieldType name="_yz_str" class="solr.StrField" sortMissingLast="true" />
+    <!-- Used for the catch all field -->
+    <fieldtype name="ignored" stored="false" indexed="false" multiValued="true" class="solr.StrField" />
+ </types>
+</schema>
diff --git a/infrastructure/cdn-in-a-box/variables.env b/infrastructure/cdn-in-a-box/variables.env
index e4817ff7e..e938cc55e 100644
--- a/infrastructure/cdn-in-a-box/variables.env
+++ b/infrastructure/cdn-in-a-box/variables.env
@@ -14,28 +14,70 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-CERT_CITY=Denver
-CERT_COMPANY=NotComcast
-CERT_COUNTRY=US
-CERT_STATE=CO
+TLD_DOMAIN=ciab.test
+INFRA_SUBDOMAIN=infra
+CDN_SUBDOMAIN=mycdn
+DS_HOSTS=demo1 demo2 demo3
+X509_CA_NAME=CIAB-CA
+X509_CA_COUNTRY=US
+X509_CA_STATE=Colorado
+X509_CA_CITY=Denver
+X509_CA_COMPANY=NotComcast
+X509_CA_ORG=CDN-in-a-Box
+X509_CA_ORGUNIT=CDN-in-a-Box
+X509_CA_EMAIL=no-reply@infra.ciab.test
+X509_CA_DIGEST=sha256
+X509_CA_DURATION_DAYS=365
+X509_CA_KEYTYPE=rsa
+X509_CA_KEYSIZE=4096
+X509_CA_UMASK="0000"
+X509_CA_DIR=/shared/ssl
+X509_CA_DONE_FILE=/shared/ssl/completed
+X509_CA_ENV_FILE=/shared/ssl/environment
 DB_NAME=traffic_ops
 DB_PORT=5432
 DB_SERVER=db
 DB_USER=traffic_ops
 DB_USER_PASS=twelve
+DNS_SERVER=dns
 DBIC_TRACE=0
-DOMAIN=cdn.local
+ENROLLER_HOST=enroller
 PGPASSWORD=twelve
 POSTGRES_PASSWORD=twelve
-TM_EMAIL=tmonitor@example.com
+EDGE_HOST=edge
+MID_HOST=mid
+ORIGIN_HOST=origin
+TM_HOST=trafficmonitor
+TM_PORT=80
+TM_EMAIL=tmonitor@cdn.example.com
 TM_PASSWORD=jhdslvhdfsuklvfhsuvlhs
 TM_USER=tmon
 TO_ADMIN_PASSWORD=twelve
 TO_ADMIN_USER=admin
 TO_EMAIL=cdnadmin@example.com
 TO_HOST=trafficops
-TO_PORT=6443
+TO_PORT=443
+TO_PERL_HOST=trafficops-perl
+TO_PERL_PORT=443
 TO_SECRET=blahblah
-TP_EMAIL=none
 TP_HOST=trafficportal
+TP_EMAIL=tp@cdn.example.com
+TR_HOST=trafficrouter
+TR_DNS_PORT=53
+TR_HTTP_PORT=80
+TR_HTTPS_PORT=443
+TR_API_PORT=3333
+TP_PORT=443
+TS_HOST=trafficstats
 TV_HOST=trafficvault
+TV_USER=tvault
+TV_PASSWORD=mwL5GP6Ghu_uJpkfjfiBmii3l9vfgLl0
+TV_EMAIL=tvault@cdn.example.com
+TV_ADMIN_USER=admin
+TV_ADMIN_PASSWORD=riakAdmin
+TV_RIAK_USER=riakuser
+TV_RIAK_PASSWORD=riakPassword
+TV_INT_PORT=8087
+TV_HTTP_PORT=8098
+TV_HTTPS_PORT=8088
+ENROLLER_DIR=/shared/enroller


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services