diff --git a/ansible/cheaha.yml b/ansible/cheaha.yml
index bfb1af113f50383b600441092d4549aef7a82901..910268e05f7923f1a20b6828fa30ea11cf4cec2e 100644
--- a/ansible/cheaha.yml
+++ b/ansible/cheaha.yml
@@ -1,9 +1,10 @@
 ---
 - name: Setup node for use as a virtual cheaha node
-  hosts: default
+  hosts: all
   become: true
   roles:
+    - { name: 'fix_centos_repo', tags: 'fix_centos_repo' }
     - { name: 'cheaha.node', tags: 'cheaha.node' }
     - { name: 'nfs_mounts', tags: 'nfs_mounts' }
     - { name: 'ldap_config', tags: 'ldap_config' }
-    - { name: 'slurm_client', tags: 'slurm_client' }
+    - { name: 'slurm_client', tags: 'slurm_client', when: enable_slurm_client }
diff --git a/ansible/group_vars/all b/ansible/group_vars/all
index e55be3c6b5c2ad77a3195681900b9b84a6a25585..d1d270fa523e65f3c10a5a10ad271916431c8a99 100644
--- a/ansible/group_vars/all
+++ b/ansible/group_vars/all
@@ -4,9 +4,27 @@
   yum_repo_files: []
   pkg_list: []
   slurm_version: 18.08.9
-  
+  enable_slurm_client: false
+
 # NHC related
   nhc_download_url: "https://github.com/mej/nhc/releases/download/1.4.3/lbnl-nhc-1.4.3-1.el7.noarch.rpm"
   nhc_download_path: "/tmp"
   nhc_git_repo: "https://gitlab.rc.uab.edu/rc/nhc.git"
   nhc_git_repo_path: "/tmp/nhc"
+
+  root_ssh_key: ""
+
+# cheaha.node related
+  hostname_lookup_table:
+    - "10.141.255.254 master.cm.cluster master localmaster.cm.cluster localmaster ldapserver.cm.cluster ldapserver"
+
+# ldap_config related
+  ldap_cert_path: "/etc/openldap/certs"
+  ldap_uri: "ldap://ldapserver"
+
+# nfs_mounts related
+  use_autofs: false
+  mount_points:
+    - /gpfs4
+    - /gpfs5
+
diff --git a/ansible/group_vars/prod b/ansible/group_vars/prod
new file mode 100644
index 0000000000000000000000000000000000000000..ee662aad4834e0ac34731f05364c7b6a4639af76
--- /dev/null
+++ b/ansible/group_vars/prod
@@ -0,0 +1,9 @@
+---
+  hostname_lookup_table:
+    - "172.20.0.24 cheaha-master02.cm.cluster cheaha-master02"
+    - "172.20.0.22 cheaha-master01.cm.cluster cheaha-master01"
+    - "172.20.0.25 master.cm.cluster master localmaster.cm.cluster localmaster ldapserver.cm.cluster ldapserver"
+
+  bright_openldap_path: "/cm/local/apps/openldap"
+  ldap_cert_path: "{{bright_openldap_path}}/etc/certs"
+  ldap_uri: "ldaps://ldapserver"
diff --git a/ansible/roles/cheaha.node/tasks/main.yml b/ansible/roles/cheaha.node/tasks/main.yml
index 99ca7f3e9ff8f1185e715dfac56767846a176af9..c5a171f72c658838a15b4115aef863ec157a62cf 100644
--- a/ansible/roles/cheaha.node/tasks/main.yml
+++ b/ansible/roles/cheaha.node/tasks/main.yml
@@ -4,9 +4,7 @@
     path: /etc/hosts
     line: "{{ item }}"
   loop:
-    - "172.20.0.24 cheaha-master02.cm.cluster cheaha-master02"
-    - "172.20.0.22 cheaha-master01.cm.cluster cheaha-master01"
-    - "172.20.0.25 master.cm.cluster master localmaster.cm.cluster localmaster ldapserver.cm.cluster ldapserver"
+    "{{ hostname_lookup_table }}"
 
 - name: Add proper DNS search to lookup other nodes on the cluster
   ansible.builtin.lineinfile:
@@ -25,6 +23,7 @@
     owner: root
     group: root
     mode: 0644
+  when: "'cm.repo' in yum_repo_files"
 
 - name: Add ssh key for root access
   ansible.posix.authorized_key:
diff --git a/ansible/roles/ldap_config/tasks/main.yml b/ansible/roles/ldap_config/tasks/main.yml
index 183261006200403e678d54a3e0fd84d1453f9174..0f8db2afbbdfab54b1546440e9b2ea3df2b1f4ac 100644
--- a/ansible/roles/ldap_config/tasks/main.yml
+++ b/ansible/roles/ldap_config/tasks/main.yml
@@ -25,7 +25,7 @@
 - name: Copy ldap cert(s) into place
   ansible.builtin.copy:
     src: "{{ item.src }}"
-    dest: "/cm/local/apps/openldap/etc/certs/{{ item.src }}"
+    dest: "{{ ldap_cert_path }}/{{ item.src }}"
     owner: ldap
     group: ldap
     mode: 0440
@@ -33,10 +33,11 @@
     - { src: ca.pem }
     - { src: ldap.key }
     - { src: ldap.pem }
+  when: ldap_uri | regex_search('^ldaps://')
 
 - name: Copy ldap config into place
-  ansible.builtin.copy:
-    src: nslcd.conf
+  ansible.builtin.template:
+    src: nslcd.conf.j2
     dest: /etc/nslcd.conf
     owner: root
     group: root
@@ -46,5 +47,6 @@
   ansible.builtin.service:
     name: "{{ item }}"
     enabled: yes
+    state: restarted
   loop:
     - nslcd
diff --git a/ansible/roles/ldap_config/templates/nslcd.conf.j2 b/ansible/roles/ldap_config/templates/nslcd.conf.j2
new file mode 100644
index 0000000000000000000000000000000000000000..0d03cdfb7f64277046f48afbf5810916f3f5b04f
--- /dev/null
+++ b/ansible/roles/ldap_config/templates/nslcd.conf.j2
@@ -0,0 +1,148 @@
+# This is the configuration file for the LDAP nameservice
+# switch library's nslcd daemon. It configures the mapping
+# between NSS names (see /etc/nsswitch.conf) and LDAP
+# information in the directory.
+# See the manual page nslcd.conf(5) for more information.
+
+# The user and group nslcd should run as.
+uid nslcd
+gid ldap
+
+# The uri pointing to the LDAP server to use for name lookups.
+# Multiple entries may be specified. The address that is used
+# here should be resolvable without using LDAP (obviously).
+#uri ldap://127.0.0.1/
+#uri ldaps://127.0.0.1/
+#uri ldapi://%2fvar%2frun%2fldapi_sock/
+# Note: %2f encodes the '/' used as directory separator
+uri {{ ldap_uri }}
+
+# The LDAP version to use (defaults to 3
+# if supported by client library)
+#ldap_version 3
+
+# The distinguished name of the search base.
+base dc=cm,dc=cluster
+
+# The distinguished name to bind to the server with.
+# Optional: default is to bind anonymously.
+#binddn cn=proxyuser,dc=example,dc=com
+
+# The credentials to bind with.
+# Optional: default is no credentials.
+# Note that if you set a bindpw you should check the permissions of this file.
+#bindpw secret
+
+# The distinguished name to perform password modifications by root by.
+#rootpwmoddn cn=admin,dc=example,dc=com
+
+# The default search scope.
+#scope sub
+#scope one
+#scope base
+
+# Customize certain database lookups.
+#base   group  ou=Groups,dc=example,dc=com
+#base   passwd ou=People,dc=example,dc=com
+#base   shadow ou=People,dc=example,dc=com
+#scope  group  onelevel
+#scope  hosts  sub
+
+# Bind/connect timelimit.
+#bind_timelimit 30
+
+# Search timelimit.
+#timelimit 30
+
+# Idle timelimit. nslcd will close connections if the
+# server has not been contacted for the number of seconds.
+idle_timelimit 240
+
+# Use StartTLS without verifying the server certificate.
+#ssl start_tls
+#tls_reqcert never
+
+{% if ldap_uri | regex_search('^ldaps://') %}
+ssl on
+tls_reqcert demand
+
+# CA certificates for server certificate verification
+#tls_cacertdir /etc/ssl/certs
+tls_cacertfile /cm/local/apps/openldap/etc/certs/ca.pem
+tls_cert /cm/local/apps/openldap/etc/certs/ldap.pem
+tls_key /cm/local/apps/openldap/etc/certs/ldap.key
+{% endif %}
+
+# Seed the PRNG if /dev/urandom is not provided
+#tls_randfile /var/run/egd-pool
+
+# SSL cipher suite
+# See man ciphers for syntax
+#tls_ciphers TLSv1
+
+# Client certificate and key
+# Use these, if your server requires client authentication.
+
+# Mappings for Services for UNIX 3.5
+#filter passwd (objectClass=User)
+#map    passwd uid              msSFU30Name
+#map    passwd userPassword     msSFU30Password
+#map    passwd homeDirectory    msSFU30HomeDirectory
+#map    passwd homeDirectory    msSFUHomeDirectory
+#filter shadow (objectClass=User)
+#map    shadow uid              msSFU30Name
+#map    shadow userPassword     msSFU30Password
+#filter group  (objectClass=Group)
+#map    group  member           msSFU30PosixMember
+
+# Mappings for Services for UNIX 2.0
+#filter passwd (objectClass=User)
+#map    passwd uid              msSFUName
+#map    passwd userPassword     msSFUPassword
+#map    passwd homeDirectory    msSFUHomeDirectory
+#map    passwd gecos            msSFUName
+#filter shadow (objectClass=User)
+#map    shadow uid              msSFUName
+#map    shadow userPassword     msSFUPassword
+#map    shadow shadowLastChange pwdLastSet
+#filter group  (objectClass=Group)
+#map    group  member           posixMember
+
+# Mappings for Active Directory
+#pagesize 1000
+#referrals off
+#idle_timelimit 800
+#filter passwd (&(objectClass=user)(!(objectClass=computer))(uidNumber=*)(unixHomeDirectory=*))
+#map    passwd uid              sAMAccountName
+#map    passwd homeDirectory    unixHomeDirectory
+#map    passwd gecos            displayName
+#filter shadow (&(objectClass=user)(!(objectClass=computer))(uidNumber=*)(unixHomeDirectory=*))
+#map    shadow uid              sAMAccountName
+#map    shadow shadowLastChange pwdLastSet
+#filter group  (objectClass=group)
+
+# Alternative mappings for Active Directory
+# (replace the SIDs in the objectSid mappings with the value for your domain)
+#pagesize 1000
+#referrals off
+#idle_timelimit 800
+#filter passwd (&(objectClass=user)(objectClass=person)(!(objectClass=computer)))
+#map    passwd uid           cn
+#map    passwd uidNumber     objectSid:S-1-5-21-3623811015-3361044348-30300820
+#map    passwd gidNumber     objectSid:S-1-5-21-3623811015-3361044348-30300820
+#map    passwd homeDirectory "/home/$cn"
+#map    passwd gecos         displayName
+#map    passwd loginShell    "/bin/bash"
+#filter group (|(objectClass=group)(objectClass=person))
+#map    group gidNumber      objectSid:S-1-5-21-3623811015-3361044348-30300820
+
+# Mappings for AIX SecureWay
+#filter passwd (objectClass=aixAccount)
+#map    passwd uid              userName
+#map    passwd userPassword     passwordChar
+#map    passwd uidNumber        uid
+#map    passwd gidNumber        gid
+#filter group  (objectClass=aixAccessGroup)
+#map    group  cn               groupName
+#map    group  gidNumber        gid
+# This comment prevents repeated auto-migration of settings.
diff --git a/ansible/roles/nfs_mounts/tasks/autofs.yml b/ansible/roles/nfs_mounts/tasks/autofs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..39ba56e6d4f2ab399f6c80ff157cf6a51b4e40ff
--- /dev/null
+++ b/ansible/roles/nfs_mounts/tasks/autofs.yml
@@ -0,0 +1,66 @@
+---
+- name: Create base directories
+  ansible.builtin.file:
+    path: "{{ item.dir }}"
+    state: directory
+    mode: "{{ item.mode }}"
+  loop:
+    - { dir: /local, mode: '0777' }
+    - { dir: /scratch, mode: '0755' }
+    - { dir: /share, mode: '0755' }
+    - { dir: /data/rc/apps, mode: '0755' } # this is only required for the symlink to be happy
+    - { dir: /data/user, mode: '0755' }
+    - { dir: /data/project, mode: '0755' }
+
+- name: Remove unused entry in master map
+  ansible.builtin.replace:
+    dest: /etc/auto.master
+    regexp: '{{ item.regexp }}'
+    replace: '{{ item.replace }}'
+    backup: true
+  loop:
+    - { regexp: '^(/misc)', replace: '#\1' }
+    - { regexp: '^(/net)', replace: '#\1' }
+    - { regexp: '^(\+auto.master)', replace: '#\1' }
+
+- name: Add master map file
+  ansible.builtin.lineinfile:
+    path: "/etc/auto.master.d/gpfs.autofs"
+    line: "{{ item.mount_point }} /etc/auto.{{ item.map_name }}"
+    create: yes
+  loop:
+    - { mount_point: "/cm/shared", map_name: "cm-share" }
+    - { mount_point: "/data/project", map_name: "data-project" }
+    - { mount_point: "/data/user", map_name: "data-user" }
+    - { mount_point: "/data/rc/apps", map_name: "data-rc-apps" }
+    - { mount_point: "/-", map_name: "scratch" }
+    - { mount_point: "/home", map_name: "home" }
+
+- name: Set up autofs map files
+  ansible.builtin.lineinfile:
+    path: "/etc/auto.{{ item.map_name }}"
+    line: "{{ item.key }} -{{ item.opts }} {{ item.src }}"
+    create: true
+  loop:
+    - { map_name: "cm-share", key: "*", src: "gpfs.rc.uab.edu:/data/cm/shared-8.2/&", opts: "fstype=nfs,vers=3,_netdev,defaults" }
+    - { map_name: "data-project", key: "*", src: "gpfs.rc.uab.edu:/data/project/&", opts: "fstype=nfs,vers=3,_netdev,defaults" }
+    - { map_name: "data-user", key: "*", src: "gpfs.rc.uab.edu:/data/user/&", opts: "fstype=nfs,vers=3,_netdev,local_lock=posix,defaults" }
+    - { map_name: "data-rc-apps", key: "*", src: "gpfs.rc.uab.edu:/data/rc/apps/&", opts: "fstype=nfs,vers=3,_netdev,defaults" }
+    - { map_name: "scratch", key: "/scratch", src: "gpfs.rc.uab.edu:/scratch", opts: "fstype=nfs,vers=3,_netdev,local_lock=posix,defaults" }
+    - { map_name: "home", key: "*", src: ":/data/user/home/&", opts: 'fstype=bind' }
+
+- name: Create symbolic links
+  ansible.builtin.file:
+    src: "{{ item.src }}"
+    dest: "{{ item.dest }}"
+    owner: root
+    group: root
+    force: yes
+    state: link
+  loop:
+    - { src: /data/rc/apps, dest: /share/apps }
+
+- name: Enable autofs service
+  ansible.builtin.service:
+    name: autofs
+    enabled: true
diff --git a/ansible/roles/nfs_mounts/tasks/fstab.yml b/ansible/roles/nfs_mounts/tasks/fstab.yml
new file mode 100644
index 0000000000000000000000000000000000000000..44c3124b9c7ea66a03f6cdc7cbbc1c807a054269
--- /dev/null
+++ b/ansible/roles/nfs_mounts/tasks/fstab.yml
@@ -0,0 +1,18 @@
+---
+- name: Create base directories
+  ansible.builtin.file:
+    path: "{{ item }}"
+    state: directory
+    mode: '0755'
+  loop:
+    "{{ mount_points }}"
+
+- name: Make an entry in the fstab
+  ansible.posix.mount:
+    src: "master:{{ item }}"
+    path: "{{ item }}"
+    opts: rw,sync,hard
+    state: present
+    fstype: nfs
+  loop:
+    "{{ mount_points }}"
diff --git a/ansible/roles/nfs_mounts/tasks/main.yml b/ansible/roles/nfs_mounts/tasks/main.yml
index 39ba56e6d4f2ab399f6c80ff157cf6a51b4e40ff..507f4c6e0c6700830aeb7b96479ff400570bddfe 100644
--- a/ansible/roles/nfs_mounts/tasks/main.yml
+++ b/ansible/roles/nfs_mounts/tasks/main.yml
@@ -1,66 +1,8 @@
 ---
-- name: Create base directories
-  ansible.builtin.file:
-    path: "{{ item.dir }}"
-    state: directory
-    mode: "{{ item.mode }}"
-  loop:
-    - { dir: /local, mode: '0777' }
-    - { dir: /scratch, mode: '0755' }
-    - { dir: /share, mode: '0755' }
-    - { dir: /data/rc/apps, mode: '0755' } # this is only required for the symlink to be happy
-    - { dir: /data/user, mode: '0755' }
-    - { dir: /data/project, mode: '0755' }
+- name: nfs_mounts using fstab
+  include_tasks: fstab.yml
+  when: not use_autofs
 
-- name: Remove unused entry in master map
-  ansible.builtin.replace:
-    dest: /etc/auto.master
-    regexp: '{{ item.regexp }}'
-    replace: '{{ item.replace }}'
-    backup: true
-  loop:
-    - { regexp: '^(/misc)', replace: '#\1' }
-    - { regexp: '^(/net)', replace: '#\1' }
-    - { regexp: '^(\+auto.master)', replace: '#\1' }
-
-- name: Add master map file
-  ansible.builtin.lineinfile:
-    path: "/etc/auto.master.d/gpfs.autofs"
-    line: "{{ item.mount_point }} /etc/auto.{{ item.map_name }}"
-    create: yes
-  loop:
-    - { mount_point: "/cm/shared", map_name: "cm-share" }
-    - { mount_point: "/data/project", map_name: "data-project" }
-    - { mount_point: "/data/user", map_name: "data-user" }
-    - { mount_point: "/data/rc/apps", map_name: "data-rc-apps" }
-    - { mount_point: "/-", map_name: "scratch" }
-    - { mount_point: "/home", map_name: "home" }
-
-- name: Set up autofs map files
-  ansible.builtin.lineinfile:
-    path: "/etc/auto.{{ item.map_name }}"
-    line: "{{ item.key }} -{{ item.opts }} {{ item.src }}"
-    create: true
-  loop:
-    - { map_name: "cm-share", key: "*", src: "gpfs.rc.uab.edu:/data/cm/shared-8.2/&", opts: "fstype=nfs,vers=3,_netdev,defaults" }
-    - { map_name: "data-project", key: "*", src: "gpfs.rc.uab.edu:/data/project/&", opts: "fstype=nfs,vers=3,_netdev,defaults" }
-    - { map_name: "data-user", key: "*", src: "gpfs.rc.uab.edu:/data/user/&", opts: "fstype=nfs,vers=3,_netdev,local_lock=posix,defaults" }
-    - { map_name: "data-rc-apps", key: "*", src: "gpfs.rc.uab.edu:/data/rc/apps/&", opts: "fstype=nfs,vers=3,_netdev,defaults" }
-    - { map_name: "scratch", key: "/scratch", src: "gpfs.rc.uab.edu:/scratch", opts: "fstype=nfs,vers=3,_netdev,local_lock=posix,defaults" }
-    - { map_name: "home", key: "*", src: ":/data/user/home/&", opts: 'fstype=bind' }
-
-- name: Create symbolic links
-  ansible.builtin.file:
-    src: "{{ item.src }}"
-    dest: "{{ item.dest }}"
-    owner: root
-    group: root
-    force: yes
-    state: link
-  loop:
-    - { src: /data/rc/apps, dest: /share/apps }
-
-- name: Enable autofs service
-  ansible.builtin.service:
-    name: autofs
-    enabled: true
+- name: nfs_mounts using autofs
+  include_tasks: autofs.yml
+  when: use_autofs