init commit

This commit is contained in:
2025-07-21 14:26:07 -05:00
commit 6a683a7a38
25 changed files with 635 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
---
- name: Add SSH key from Gitea
hosts: all
become: true
tasks:
- name: Ensure .ssh directory exists
file:
path: "/home/{{ username }}/.ssh"
state: directory
owner: "{{ username }}"
group: "{{ username }}"
mode: '0700'
- name: Add public key from Gitea to authorized_keys
ansible.posix.authorized_key:
user: "{{ username }}"
key: "https://gitea.purpleraft.com/{{ username }}.keys"
state: present
manage_dir: false # we already ensured it

View File

@@ -0,0 +1,29 @@
---
- name: Ensure user is in specified group
hosts: all
become: true
gather_facts: false
vars:
check_user: "{{ check_user }}"
check_group: "{{ check_group }}"
tasks:
- name: Ensure group exists
group:
name: "{{ check_group }}"
state: present
- name: Check if user exists
getent:
database: passwd
key: "{{ check_user }}"
register: user_check
changed_when: false
failed_when: user_check.ansible_facts.getent_passwd[check_user] is not defined
- name: Add user to group (non-destructively)
user:
name: "{{ check_user }}"
groups: "{{ check_group }}"
append: true

View File

@@ -0,0 +1,24 @@
- name: Check if reboot is required
hosts: all
gather_facts: false
become: true
vars:
reboot_machines: false # default, override via --extra-vars
tasks:
- name: Check for /var/run/reboot-required
stat:
path: /var/run/reboot-required
register: reboot_flag
- name: Display reboot status
debug:
msg: "{{ inventory_hostname }} {{ 'REQUIRES' if reboot_flag.stat.exists else 'does NOT require' }} a reboot."
- name: Reboot if required and reboot_machines flag is true
reboot:
msg: "Rebooting due to /var/run/reboot-required"
pre_reboot_delay: 5
when:
- reboot_flag.stat.exists
- reboot_machines | bool

View File

@@ -0,0 +1,32 @@
- name: Quick check of time offset using ntpdate
hosts: all
become: true
gather_facts: false
vars:
ntp_check_target: "pool.ntp.org"
tasks:
- name: Ensure ntpdate is installed
apt:
name: ntpdate
state: present
update_cache: true
- name: Query time offset from {{ ntp_check_target }}
command: "ntpdate -q {{ ntp_check_target }}"
register: ntp_offset
changed_when: false
failed_when: ntp_offset.rc != 0
- name: Try to extract final offset line
set_fact:
ntp_offset_summary: "{{ ntp_offset.stdout_lines | select('search', 'adjust time') | list | first | default('No offset line found') }}"
- name: Show full ntpdate output per host
debug:
msg: |
[{{ inventory_hostname }}]
Offset summary: {{ ntp_offset_summary }}
Raw output:
{{ ntp_offset.stdout_lines | join('\n') }}

View File

@@ -0,0 +1,31 @@
---
- name: Check if user is in specified group
hosts: all
gather_facts: false
become: true
vars_prompt:
- name: check_user
prompt: "Enter the username to check"
private: no
- name: check_group
prompt: "Enter the group to verify membership"
private: no
tasks:
- name: Get groups for specified user
ansible.builtin.command: "id -nG {{ check_user }}"
register: user_groups
changed_when: false
failed_when: user_groups.rc != 0
- name: Set fact if user is in group
set_fact:
user_in_group: "{{ check_group in user_groups.stdout.split() }}"
- name: Report user group membership
debug:
msg: >
User '{{ check_user }}' {{ 'IS' if user_in_group else 'IS NOT' }}
in the '{{ check_group }}' group on {{ inventory_hostname }}.

View File

@@ -0,0 +1,46 @@
---
- name: Check unallocated disk space
hosts: all
become: true
gather_facts: false
tasks:
- name: Get block device sizes
command: lsblk -b -o NAME,SIZE,TYPE -dn
register: lsblk_output
- name: Parse lsblk output into structured facts
set_fact:
disks: "{{ disks | default({}) | combine({ item.split()[0]: item.split()[1]|int }) }}"
loop: "{{ lsblk_output.stdout_lines }}"
when: "'disk' in item"
- name: Get partition sizes for each disk
command: lsblk -b -o NAME,SIZE,TYPE -n
register: partition_output
- name: Initialize disk usage map
set_fact:
used_space: "{{ used_space | default({}) }}"
- name: Sum partition sizes under each disk
set_fact:
used_space: >-
{{
used_space | combine({
(item.0): (used_space[item.0]|default(0)) + (item.1.split()[1]|int)
})
}}
with_nested:
- "{{ disks.keys() | list }}"
- "{{ partition_output.stdout_lines }}"
when: item.1.startswith(item.0) and 'part' in item.1
- name: Show disk usage summary
debug:
msg: >-
Disk /dev/{{ item.key }}: Total {{ item.value|int // 1024**3 }} GB,
Used {{ used_space[item.key]|default(0)|int // 1024**3 }} GB,
Unallocated {{ (item.value|int - used_space[item.key]|default(0)|int) // 1024**3 }} GB
loop: "{{ disks|dict2items }}"

View File

@@ -0,0 +1,51 @@
- name: Configure Chrony NTP Client
hosts: all
become: true
tasks:
- name: Ensure chrony is installed
apt:
name: chrony
state: present
update_cache: true
- name: Disable systemd-timesyncd if present
systemd:
name: systemd-timesyncd
enabled: false
state: stopped
ignore_errors: true
- name: Ensure sources.d directory exists
file:
path: /etc/chrony/sources.d
state: directory
mode: "0755"
- name: Deploy atomic source
copy:
src: templates/ntp/atomic.sources
dest: /etc/chrony/sources.d/atomic.sources
owner: root
group: root
mode: "0644"
- name: Deploy navy source
copy:
src: templates/ntp/navy.sources
dest: /etc/chrony/sources.d/navy.sources
owner: root
group: root
mode: "0644"
- name: Deploy fallback pool
copy:
src: templates/ntp/fallback.sources
dest: /etc/chrony/sources.d/fallback.sources
owner: root
group: root
mode: "0644"
- name: Reload NTP sources
command: chronyc reload sources
register: reload_output
changed_when: "'Sources reloaded' in reload_output.stdout"

35
playbooks/create-user.yml Normal file
View File

@@ -0,0 +1,35 @@
---
- name: Create a user with SSH access and optional groups
hosts: all
become: true
gather_facts: false
vars:
username: "{{ username }}"
authorized_key: "{{ authorized_key }}"
extra_groups: "{{ extra_groups | default('') }}"
extra_groups_list: "{{ extra_groups.split(',') | map('trim') | list if extra_groups else [] }}"
default_shell: "{{ default_shell | default('/bin/bash') }}"
tasks:
- name: Ensure each extra group exists
ansible.builtin.group:
name: "{{ item }}"
state: present
loop: "{{ extra_groups_list }}"
when: extra_groups_list | length > 0
- name: Ensure user account exists
ansible.builtin.user:
name: "{{ username }}"
shell: "{{ default_shell }}"
groups: "{{ extra_groups_list }}"
append: true
create_home: true
state: present
- name: Set authorized SSH key
ansible.builtin.authorized_key:
user: "{{ username }}"
key: "{{ authorized_key }}"
state: present

View File

@@ -0,0 +1,43 @@
---
- name: Deploy complete SSH server configuration
hosts: all
become: true
gather_facts: false
tasks:
- name: Deploy base /etc/ssh/sshd_config file
template:
src: templates/sshd/sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: "0644"
notify: Reload SSH
- name: Deploy hardened global ssh config include
template:
src: templates/sshd/00-global.conf.j2
dest: /etc/ssh/sshd_config.d/00-global.conf
owner: root
group: root
mode: "0644"
notify: Reload SSH
- name: Deploy LAN password bypass config include
template:
src: templates/sshd/99-lan-bypass.conf.j2
dest: /etc/ssh/sshd_config.d/99-lan-bypass.conf
owner: root
group: root
mode: "0644"
notify: Reload SSH
- name: Validate sshd configuration syntax
command: sshd -t
changed_when: false
handlers:
- name: Reload SSH
service:
name: ssh
state: reloaded

View File

@@ -0,0 +1,18 @@
---
- name: Allow user to set their own password
hosts: all
become: true
gather_facts: false
vars:
username: "{{ username }}"
tasks:
- name: Unlock password so user can run passwd
ansible.builtin.command: "passwd -d {{ username }}"
when: not ansible_check_mode
- name: Ensure user is not locked
ansible.builtin.user:
name: "{{ username }}"
password_lock: false

View File

@@ -0,0 +1,22 @@
---
- name: Ensure essential tools are installed on all hosts
hosts: all
become: true
vars:
essential_packages:
- curl
- git
- jq
- htop
- unzip
- ca-certificates
- net-tools
- acl
tasks:
- name: Install common tools
apt:
name: "{{ essential_packages }}"
state: present
update_cache: yes

View File

@@ -0,0 +1,146 @@
---
- name: Install Docker using official Docker documentation steps and set up /opt/docker and /srv/docker
hosts: docker
become: true
gather_facts: true
vars:
docker_keyring_path: /etc/apt/keyrings/docker.asc
docker_repo_list_path: /etc/apt/sources.list.d/docker.list
docker_acl_path: /opt/docker
srv_docker_path: /srv/docker
docker_data_user: dockeruser
docker_data_group: dockerdata
docker_data_uid: 2011
docker_data_gid: 2011
tasks:
# --- Prereqs ---
- name: Ensure required packages are installed
apt:
name:
- ca-certificates
- curl
- acl # Required for setfacl
state: present
update_cache: yes
- name: Ensure keyring directory exists
file:
path: /etc/apt/keyrings
state: directory
mode: "0755"
- name: Download Docker's official GPG key
get_url:
url: https://download.docker.com/linux/ubuntu/gpg
dest: "{{ docker_keyring_path }}"
mode: "0644"
register: docker_key_download
- name: Get native architecture (dpkg --print-architecture)
command: dpkg --print-architecture
register: dpkg_arch_result
changed_when: false
- name: Add Docker repository to Apt sources
copy:
dest: "{{ docker_repo_list_path }}"
content: |
deb [arch={{ dpkg_arch_result.stdout }} signed-by={{ docker_keyring_path }}] https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename }} stable
notify: Update apt cache
- name: Flush handlers to update apt cache before install
meta: flush_handlers
# --- Docker Install ---
- name: Install Docker packages
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: no
- name: Ensure docker group exists
group:
name: docker
state: present
- name: Ensure Docker service is enabled and running
systemd:
name: docker
enabled: true
state: started
# --- ACL & Folder Standardization ---
- name: Ensure Docker base folder exists with correct ownership
file:
path: "{{ docker_acl_path }}"
state: directory
owner: root
group: docker
mode: "0775"
- name: Set setgid bit on /opt/docker so group is inherited
file:
path: "{{ docker_acl_path }}"
mode: "2775"
- name: Check for existing default ACL on Docker folder
command: getfacl --access --default {{ docker_acl_path }}
register: facl_check
changed_when: false
failed_when: false
- name: Set default ACL for docker group if not already present
command: setfacl -d -m g:docker:rwx {{ docker_acl_path }}
when: "'group:docker:rwx' not in facl_check.stdout"
# --- New: Dedicated Docker Data User/Group and /srv/docker Setup ---
- name: Create docker data group with fixed GID
group:
name: "{{ docker_data_group }}"
gid: "{{ docker_data_gid }}"
state: present
system: yes
- name: Create docker data user with fixed UID and GID
user:
name: "{{ docker_data_user }}"
uid: "{{ docker_data_uid }}"
group: "{{ docker_data_group }}"
shell: /usr/sbin/nologin
create_home: no
system: yes
state: present
- name: Ensure /srv/docker exists with correct ownership
file:
path: "{{ srv_docker_path }}"
state: directory
owner: "{{ docker_data_user }}"
group: "{{ docker_data_group }}"
mode: "0770"
- name: Set setgid bit on /srv/docker so group is inherited
file:
path: "{{ srv_docker_path }}"
mode: "2770"
- name: Set default ACL for dockerdata group on /srv/docker
ansible.posix.acl:
path: "{{ srv_docker_path }}"
entity: "{{ docker_data_group }}"
etype: group
permissions: rwx
default: yes
state: present
handlers:
- name: Update apt cache
apt:
update_cache: yes

View File

@@ -0,0 +1,39 @@
- name: Check time synchronization using ntpdig (modern method)
hosts: all
become: true
gather_facts: false
vars:
ntp_check_target: "pool.ntp.org"
tasks:
- name: Remove legacy ntpdate package (if present)
apt:
name: ntpdate
state: absent
- name: Ensure ntpsec-ntpdate is installed (provides ntpdig)
apt:
name: ntpsec-ntpdate
state: present
update_cache: true
- name: Query time offset using ntpdig from {{ ntp_check_target }}
command: "ntpdig {{ ntp_check_target }}"
register: ntpdig_output
changed_when: false
failed_when: ntpdig_output.rc != 0
- name: Parse correct offset and error from ntpdig output
set_fact:
ntpdig_offset: "{{ ntpdig_output.stdout | regex_search('[+-][0-9]+\\.[0-9]+(?=\\s+\\+/-)', '\\0') | default('N/A') }}"
ntpdig_error: "{{ ntpdig_output.stdout | regex_search('\\+/-\\s+([0-9]+\\.[0-9]+)', '\\1') | default('N/A') }}"
- name: Show parsed ntpdig result
debug:
msg: |
[{{ inventory_hostname }}]
Offset: {{ ntpdig_offset }} sec
Estimated error: ±{{ ntpdig_error }} sec
Raw output:
{{ ntpdig_output.stdout_lines | join('\n') }}

9
playbooks/reboot.yml Normal file
View File

@@ -0,0 +1,9 @@
---
- name: Reboot system immediately
hosts: all
become: true
tasks:
- name: Reboot the machine
ansible.builtin.reboot:
msg: "Rebooting"
reboot_timeout: 600

View File

@@ -0,0 +1,2 @@
# Empty role list
roles: []

View File

@@ -0,0 +1 @@
server atomicmidnight.internal.purpleraft.com iburst prefer

View File

@@ -0,0 +1,3 @@
pool 0.pool.ntp.org iburst
pool 1.pool.ntp.org iburst
pool 2.pool.ntp.org iburst

View File

@@ -0,0 +1,2 @@
server tick.usno.navy.mil iburst prefer
server tock.usno.navy.mil iburst

View File

@@ -0,0 +1,25 @@
Port 22
AddressFamily inet
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PermitEmptyPasswords no
UsePAM yes
AllowGroups {{ ssh_access_group | default('sshusers') }}
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
X11Forwarding no
PrintMotd no
PrintLastLog yes
LoginGraceTime 30s
MaxAuthTries 3
MaxSessions 2
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server

View File

@@ -0,0 +1,11 @@
Match Address 10.0.0.0/8
PasswordAuthentication yes
Match Address 192.168.0.0/16
PasswordAuthentication yes
Match Address 206.202.209.9/32
PasswordAuthentication yes
Match Address 100.64.0.0/10
PasswordAuthentication yes

View File

@@ -0,0 +1,4 @@
# Base sshd_config — managed by Ansible
# Delegates all settings to config fragments
Include /etc/ssh/sshd_config.d/*.conf

13
playbooks/test.yml Normal file
View File

@@ -0,0 +1,13 @@
- name: Semaphore connection test
hosts: all
gather_facts: false
become: true
tasks:
- name: Print the hostname
command: hostname
register: result
- name: Show result
debug:
msg: "Connected to {{ inventory_hostname }} (hostname: {{ result.stdout }})"

View File

@@ -0,0 +1,13 @@
- name: Update APT packages
hosts: all
become: true
tasks:
- name: Update package cache
apt:
update_cache: yes
- name: Upgrade packages
apt:
upgrade: safe
autoremove: yes
autoclean: yes