Compare commits

...

13 Commits

Author SHA1 Message Date
Alex Tavarez
d149361e60 set up or initialized Python project 2025-12-21 16:58:39 -05:00
Alex Tavarez
1851b0bb04 added Python lock file to version control exclusion rules 2025-12-21 16:57:41 -05:00
Alex Tavarez
88aac6b598 created environment variables file 2025-12-21 16:56:35 -05:00
Alex Tavarez
ed06773cac changed a version control exclude rule due to change in Python environment dir change 2025-12-21 16:55:59 -05:00
Alex Tavarez
0dc3edc0bc added next few tasks 2025-12-21 16:54:39 -05:00
Alex Tavarez
ea7d9f7caf changed some fields/keys/attributes and changed references to reflect those changes 2025-12-21 16:53:52 -05:00
Alex Tavarez
ffa5408e9c further clarified STDOUT message guiding user 2025-12-21 14:44:47 -05:00
Alex Tavarez
2d7f783bb9 added some more guiding STDOUT statements for commands and removed useless function 2025-12-21 14:35:56 -05:00
Alex Tavarez
eb437ce2e9 refactor: script file no longer necessary after refactor 2025-12-21 13:58:11 -05:00
Alex Tavarez
07ff003870 refactor: changed name and location of file setting environment variables, made it take shell arguments with native functions as well 2025-12-21 13:57:09 -05:00
Alex Tavarez
d88a9d6176 fix: playbook task already has access to inventory-level variables 2025-12-16 13:48:33 -05:00
Alex Tavarez
c39463f4a7 feature: added a set of default path environment variables to be used as part of execution environment 2025-12-16 13:47:47 -05:00
Alex Tavarez
474574860a fix: changed from dot notation to bracket notation for attr/meth access 2025-12-16 13:44:10 -05:00
13 changed files with 273 additions and 33 deletions

6
.bin/ansible_aliases Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
set -euo pipefail
alias ansible-galaxy="/usr/bin/ansible-galaxy"
alias ansible-vault="/usr/bin/ansible-vault"
alias ansible-playbook="/usr/bin/ansible-playbook"

187
.bin/skansible.sh Executable file
View File

@@ -0,0 +1,187 @@
#!/bin/bash
set -euo pipefail
SKATO_ANSIBLE_ROOT=$(dirname "$0")
SKATO_ANSIBLE_ROOT=$(dirname "$SKATO_ANSIBLE_ROOT")
export SKATO_ANSIBLE_ROOT
printf "root=%s\n" "$SKATO_ANSIBLE_ROOT" > "./config" # INI format
export SKATO_BOOTSTRAP_ROLE="${SKATO_ANSIBLE_ROOT}/roles/bootstrap"
export SKANSIBLE_SECRETS="${SKATO_ANSIBLE_ROOT}/.secrets"
if [[ -f "./ansible_aliases" ]]; then
source ./ansible_aliases
fi
# Relative directory paths for role templates/files
export SKANSIBLE_ARIA="aria2"
export SKANSIBLE_PROFTPD="proftpd"
export SKANSIBLE_PROFTPD_CONFS="${SKANSIBLE_PROFTPD}/conf.d"
# @NOTE below 4 filepaths have filenames that must correspond to
# the filenames in role ProFTPd templates'/files' Display settings
export SKANSIBLE_PROFTPD_CONFS_WELCOME="${SKANSIBLE_PROFTPD}/conf.d/WELCOME.txt"
export SKANSIBLE_PROFTPD_CONFS_BANNER="${SKANSIBLE_PROFTPD}/conf.d/BANNER.txt"
export SKANSIBLE_PROFTPD_CONFS_SUCCESS="${SKANSIBLE_PROFTPD}/conf.d/SUCCESS.txt"
export SKANSIBLE_PROFTPD_CONFS_EXIT="${SKANSIBLE_PROFTPD}/conf.d/BYE.txt"
export SKANSIBLE_SSHD_CONFS="sshd_config.d"
export SKANSIBLE_SYSTEMD="systemd"
export SKANSIBLE_SYSTEMD_USER_UNITS="${SKANSIBLE_SYSTEMD}/user"
export SKANSIBLE_FAIL2BAN="fail2ban"
export SKANSIBLE_FAIL2BAN_JAILS="${SKANSIBLE_FAIL2BAN}/jail.d"
export SKANSIBLE_FAIL2BAN_FILTERS="${SKANSIBLE_FAIL2BAN}/filter.d"
export SKANSIBLE_GITCONFIG_CONFS="gitconfig.d"
# @NOTE files in here must have extension "key" with IDs in
# "gpg_keys" inventory variable list as basenames.
export SKANSIBLE_GPG="gnupg"
# @NOTE files in path below must have extensions "key" (private),
# "crt" (signed), or "pem" (public) with inventory host FQDN as basename
export SKANSIBLE_SSL="ca-certificates"
set-root () {
if [[ $# -eq 0 ]]; then
SKATO_ANSIBLE_ROOT=$(awk -F "=" '/root/ {print $2}' "./config")
export SKATO_ANSIBLE_ROOT
elif [[ -z "$1" ]]; then
SKATO_ANSIBLE_ROOT="$1"
export SKATO_ANSIBLE_ROOT
sed -i 's|^(root=).*||g' "./config"
sed -i "1 i\root=${SKATO_ANSIBLE_ROOT}" "./config"
fi
}
gxy () {
ansible-galaxy "$@"
}
vult () {
ansible-vault "$@"
}
play () {
ansible-playbook "$@"
}
import-gpg () {
for id in "$@";
do
gpg --export-secret-keys "$id" > "${SKATO_BOOTSTRAP_ROLE}/files/${SKANSIBLE_GPG}/${id}.key"
printf "Please manually add GPG key with 'id' of '%s' in 'users.\$username.gpg_keys' list of inventory file." "$id"
done
printf "Please manually change ID attribute of GPG keys in 'users.\$username.gpg_keys' list of inventory file."
}
import-ssl () {
for domain in "$@";
do
cp "/usr/local/share/ca-certificates/${domain}.key" "${SKATO_BOOTSTRAP_ROLE}/files/${SKANSIBLE_SSL}/${domain}.key"
cp "/usr/local/share/ca-certificates/${domain}.pem" "${SKATO_BOOTSTRAP_ROLE}/files/${SKANSIBLE_SSL}/${domain}.pem"
cp "/usr/local/share/ca-certificates/${domain}.crt" "${SKATO_BOOTSTRAP_ROLE}/files/${SKANSIBLE_SSL}/${domain}.crt"
printf "Please manually change 'fqdn' attribute in inventory group or host variable file to '%s'." "$domain"
done
}
import () {
case "$1" in
ssl) shift; import-ssl "$@";;
gpg) shift; import-gpg "$@";;
*) exit 1;;
esac
}
decrypt () {
while getopts "mv:i:d:" flag; do
case "$flag" in
m) METHOD=$OPTARG;;
v) VAULT_ID=$OPTARG;;
i) INPUT_FILE=$OPTARG;;
d) OUTPUT_PATH=$OPTARG;;
*) exit 1;;
esac
done
if ! [[ "$VAULT_ID" == *"@"* ]]; then
ID_TAG="$VAULT_ID"
if [[ "$METHOD" == "prompt" ]]; then
VAULT_ID="${VAULT_ID}@prompt"
elif [[ "$METHOD" == "file" ]]; then
if [[ -z "$INPUT_FILE" ]]; then
exit 1
else
VAULT_ID="${VAULT_ID}@${INPUT_FILE}"
fi
else
exit 1
fi
fi
if [[ -z "$OUTPUT_PATH" ]]; then
OUTPUT_FILE="${SKANSIBLE_SECRETS}/${ID_TAG}.txt"
else
mkdir -p "${SKANSIBLE_SECRETS}/${OUTPUT_PATH}"
OUTPUT_FILE="${SKANSIBLE_SECRETS}/${OUTPUT_PATH}/${ID_TAG}.txt"
fi
ansible-vault decrypt --vault-id "$VAULT_ID" --output "$OUTPUT_FILE" "$INPUT_FILE"
}
encrypt () {
while getopts "mv:d:pn:" flag; do
case "$flag" in
m) METHOD="$OPTARG";;
v) VAULT_ID="$OPTARG";;
d) PASS_PATH="$OPTARG";;
p) read -rp "Provide intended password: " PASSWORD;;
n) VAR_NAME="$OPTARG";;
*) exit 1;;
esac
done
while [[ -z "$PASSWORD" ]]; do
printf "Password missing. \nPlease specify a password. \n"
read -rp "Provide intended password: " PASSWORD
done
if ! [[ "$VAULT_ID" == *"@"* ]]; then
ID_TAG="${VAULT_ID}"
if [[ "$METHOD" == "prompt" ]]; then
VAULT_ID="${VAULT_ID}@prompt"
elif [[ "$METHOD" == "file" ]]; then
if [[ -z "$PASS_PATH" ]]; then
PASS_FILE="${SKANSIBLE_SECRETS}/${VAULT_ID}.txt"
else
mkdir -p "${SKANSIBLE_SECRETS}/${PASS_PATH}"
PASS_FILE="${SKANSIBLE_SECRETS}/${PASS_PATH}/${VAULT_ID}.txt"
fi
printf "%s\n" "$PASSWORD" > "$PASS_FILE"
VAULT_ID="${VAULT_ID}@${PASS_FILE}"
fi
fi
printf "Make sure to copy following to appropriate location in appropriate YAML file under %s: \n" "$SKATO_ANSIBLE_ROOT"
if [[ -z "$VAR_NAME" ]]; then
ansible-vault encrypt_string --name "$VAR_NAME" --stdin-name "$VAR_NAME" --vault-id "$VAULT_ID" --output - "$PASSWORD"
else
ansible-vault encrypt_string --stdin-name "$ID_TAG" --vault-id "$VAULT_ID" --output - "$PASSWORD"
fi
YAMLS_WITH_PASSWORDS=("${SKATO_BOOTSTRAP_ROLE}/vars/main/software.yml" "${SKATO_BOOTSTRAP_ROLE}/defaults/main/software.yml")
printf "Examples of common YAML files passwords may be in: \n"
printf " 1. any YAML file in %s \n" "${SKATO_ANSIBLE_ROOT}/hostvars"
printf " 2. any YAML file in %s \n" "${SKATO_ANSIBLE_ROOT}/groupvars"
for i in "${!YAMLS_WITH_PASSWORDS[@]}"; do
printf " %u. %s \n" "$(( i + 3 ))" "${YAMLS_WITH_PASSWORDS[$i]}"
done
}
# source ./extensions.d/edit.sh
case "$1" in
set-root) shift; set-root "$1";;
gxy) shift; gxy "$@";;
vult) shift; vult "$@";;
play) shift; play "$@";;
import) shift; import "$@";;
decrypt) shift; decrypt "$@";;
encrypt) shift; encrypt "$@";;
*) exit 1;;
esac

28
.env Normal file
View File

@@ -0,0 +1,28 @@
SKATO_ANSIBLE_ROOT=$(dirname "$0")
SKATO_ANSIBLE_ROOT=$(dirname "$SKATO_ANSIBLE_ROOT")
SKATO_BOOTSTRAP_ROLE="${SKATO_ANSIBLE_ROOT}/roles/bootstrap"
SKANSIBLE_SECRETS="${SKATO_ANSIBLE_ROOT}/.secrets"
# Relative directory paths for role templates/files
SKANSIBLE_ARIA="aria2"
SKANSIBLE_PROFTPD="proftpd"
SKANSIBLE_PROFTPD_CONFS="${SKANSIBLE_PROFTPD}/conf.d"
# @NOTE below 4 filepaths have filenames that must correspond to
# the filenames in role ProFTPd templates'/files' Display settings
SKANSIBLE_PROFTPD_CONFS_WELCOME="${SKANSIBLE_PROFTPD}/conf.d/WELCOME.txt"
SKANSIBLE_PROFTPD_CONFS_BANNER="${SKANSIBLE_PROFTPD}/conf.d/BANNER.txt"
SKANSIBLE_PROFTPD_CONFS_SUCCESS="${SKANSIBLE_PROFTPD}/conf.d/SUCCESS.txt"
SKANSIBLE_PROFTPD_CONFS_EXIT="${SKANSIBLE_PROFTPD}/conf.d/BYE.txt"
SKANSIBLE_SSHD_CONFS="sshd_config.d"
SKANSIBLE_SYSTEMD="systemd"
SKANSIBLE_SYSTEMD_USER_UNITS="${SKANSIBLE_SYSTEMD}/user"
SKANSIBLE_FAIL2BAN="fail2ban"
SKANSIBLE_FAIL2BAN_JAILS="${SKANSIBLE_FAIL2BAN}/jail.d"
SKANSIBLE_FAIL2BAN_FILTERS="${SKANSIBLE_FAIL2BAN}/filter.d"
SKANSIBLE_GITCONFIG_CONFS="gitconfig.d"
# @NOTE files in here must have extension "key" with IDs in
# "gpg_keys" inventory variable list as basenames.
SKANSIBLE_GPG="gnupg"
# @NOTE files in path below must have extensions "key" (private),
# "crt" (signed), or "pem" (public) with inventory host FQDN as basename
SKANSIBLE_SSL="ca-certificates"

5
.gitignore vendored
View File

@@ -1,4 +1,4 @@
.env/
.venv/
*.bak
hosts.yml
.secrets/*
@@ -16,4 +16,5 @@ collections/
motd
banner
.galaxy_cache/
galaxy_token
galaxy_token
uv.lock

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

View File

@@ -2,7 +2,7 @@
---
# vars file
custom_vars:
generality:
shared:
ssh_authorized_keys:
- sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIIO0sbFLwfgSWpWwn4cy4cddKvV74efUMZVYTTjX2vnjAAAABHNzaDo= rika@hikiki
- sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHJqHHMplgqm8yiq4Qwisk67p9+f9sLM8tIAzuw2qkwpAAAABHNzaDo= rika@hikiki
@@ -55,7 +55,8 @@ groups:
# @NOTE key/field names SHOULD match value of 'group_name' key or field of its object
remote:
group_name: remote
type: system
type: system
id: ~
users:
# @NOTE key/field names MUST match value of 'username' key or field of its object
senpai:
@@ -81,8 +82,8 @@ users:
- sudo
- "{{ groups.remote.group_name }}"
services: [sshd]
ssh_authorized_keys: "{{ custom_vars.generality.ssh_authorized_keys }}"
ssh_private_key_paths: "{{ custom_vars.generality.ssh_private_key_paths }}"
ssh_authorized_keys: "{{ custom_vars['shared']['ssh_authorized_keys'] }}"
ssh_private_key_paths: "{{ custom_vars['shared']['ssh_private_key_paths'] }}"
ssh_private_key_path_pref: 0
gpg_keys:
- id: 558041D5CF2AB23B # @NOTE professional
@@ -128,8 +129,8 @@ users:
groups:
- "{{ groups.remote.group_name }}"
services: [proftpd,sftp,ftps]
ssh_authorized_keys: "{{ custom_vars.generality.ssh_authorized_keys }}"
ssh_private_key_paths: "{{ custom_vars.generality.ssh_private_key_paths }}"
ssh_authorized_keys: "{{ custom_vars['shared']['ssh_authorized_keys'] }}"
ssh_private_key_paths: "{{ custom_vars['shared']['ssh_private_key_paths'] }}"
ssh_private_key_path_pref: 0
gpg_keys: []
gpg_keyid_pref: 0

View File

@@ -13,7 +13,7 @@
private_ip: true
region: "{{ vps_service.region }}"
root_pass: "{{ vps_service.password }}"
tags: "{{ hostvars[inventory_hostname].keywords }}"
tags: "{{ keywords }}"
state: "{{ 'present' if vps_service.exists else 'absent' }}"
tags:
- vps_step

12
pyproject.toml Normal file
View File

@@ -0,0 +1,12 @@
[project]
name = "skansible"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"ansible>=13.1.0",
"ansible-lint>=25.12.1",
"ansible-navigator>=25.12.0",
"click>=8.3.1",
]

View File

@@ -14,7 +14,7 @@
group: "{{ item[0]['group'] | default(item[0]['username']) }}"
path: "{{ item[0]['home'] | default('/home/' ~ item[0]['username']) }}/{{ item[1]['username'] }}"
state: directory
loop: "{{ hostvars[inventory_hostname]['users'].values() | product(config['proftpd']['vusers'].values()) }}"
loop: "{{ hostvars[inventory_hostname]['users'].values() | product(config['proftpd']['users'].values()) }}"
- name: Create ProFTPd FTP public directory for anonymous logins
when: "'ftps' in item.value['services']"
ansible.builtin.file:
@@ -65,7 +65,7 @@
owner: root
path: "{{ item.value }}"
state: touch
loop: "{{ lookup('ansible.builtin.dict', config['proftpd']['auth_filepaths']) }}"
loop: "{{ lookup('ansible.builtin.dict', config['proftpd']['auth_paths']) }}"
- name: Create the virtual users
when: "not 'caddy' in item.value['services'] and not 'httpd' in item.value['services'] and not 'www-data' in item.value['services'] and not 'http' in item.value['services'] and not 'https' in item.value['services']"
ansible.builtin.command:
@@ -73,14 +73,14 @@
- ftpasswd
- --passwd
- "--name={{ item.value['username'] }}"
- "--uid=$(id -u {{ item.value['id_of'] }})"
- "--gid=$(id -g {{ item.value['gid_of'] }})"
- "--uid=$(id -u {{ item.value['id'] }})"
- "--gid=$(id -g {{ item.value['gid'] }})"
- "--home={{ hostvars[inventory_hostname]['users']['ftp']['home'] | default('/srv/ftp') }}/{{ item.value['username'] }}"
- --shell=/sbin/nologin
- --file={{ config['proftpd']['auth_filepaths']['users_path'] }}
- --file={{ config['proftpd']['auth_paths']['users'] }}
- --stdin
stdin: "{{ item.value['password'] }}"
loop: "{{ lookup('ansible.builtin.dict', config['proftpd']['vusers']) }}"
loop: "{{ lookup('ansible.builtin.dict', config['proftpd']['users']) }}"
- name: Create the virtual groups of virtual users
when: "not 'caddy' in item.value['services'] and not 'httpd' in item.value['services'] and not 'www-data' in item.value['services'] and not 'http' in item.value['services'] and not 'https' in item.value['services']"
ansible.builtin.command:
@@ -88,10 +88,10 @@
- ftpasswd
- --group
- "--name={{ item.value['username'] }}"
- "--gid=$(id -g {{ item.value['gid_of'] }})"
- "--gid=$(id -g {{ item.value['gid'] }})"
- "--member={{ item.value['username'] }}"
- --file={{ config['proftpd']['auth_filepaths']['groups_path'] }}
loop: "{{ lookup('ansible.builtin.dict', config['proftpd']['vusers']) }}"
- --file={{ config['proftpd']['auth_paths']['groups'] }}
loop: "{{ lookup('ansible.builtin.dict', config['proftpd']['users']) }}"
# @TODO create tasks in block integrating LDAP users to ProFTPd
# - name: Integrate LDAP users into ProFTPd
- name: Create ProFTPd FTPS virtual host
@@ -108,9 +108,9 @@
validate: proftpd --configtest
vars:
ftp_server_name: "{{ config['proftpd']['name'].uppercase() }}'s Archive'"
allowed_users: "{{ ','.join(list(map(lambda u: u['username'], filter(lambda u: not 'http' in u['services'] and not 'https' in u['services'] and not 'httpd' in u['services'] and not 'caddy' in u['services'] and not 'www-data' in u['services'], config['proftpd']['vusers'].values())))) }}"
allowed_users: "{{ ','.join(list(map(lambda u: u['username'], filter(lambda u: not 'http' in u['services'] and not 'https' in u['services'] and not 'httpd' in u['services'] and not 'caddy' in u['services'] and not 'www-data' in u['services'], config['proftpd']['users'].values())))) }}"
anon_root: "{{ map(lambda u: u['home'], filter(lambda u: 'ftps' in u['services'] or 'proftpd' in u['services'], hostvars[inventory_hostname]['users'].values())) | list | random }}/public"
anon_user: "{{ config['proftpd']['vusers']['smuggler']['username'] }}"
anon_user: "{{ config['proftpd']['users']['smuggler']['username'] }}"
- name: Set ProFTPd jail in fail2ban
block:
- name: Create fail2ban system configuration directory

View File

@@ -15,7 +15,7 @@ rpc-allow-origin-all=true
rpc-max-request-size=10M
rpc-listen-all=true
rpc-listen-port=6800
rpc-secret={{ config.aria.secret }}
rpc-secret={{ config['aria']['api_key'] }}
# rpc-certificate=
# rpc-private-key=
# rpc-secure=true

View File

@@ -20,8 +20,8 @@
# AuthOrder mod_auth_pam.c mod_auth_unix.c*
AuthOrder mod_auth_file.c
AuthUserFile {{ config.proftpd.auth_filepaths.users_path }}
AuthGroupFile {{ config.proftpd.auth_filepaths.groups_path }}
AuthUserFile {{ config.proftpd.auth_paths.users }}
AuthGroupFile {{ config.proftpd.auth_paths.groups }}
AuthFileOptions SyntaxCheck
TLSEngine on

View File

@@ -249,16 +249,16 @@ config:
editor: nvim
proftpd:
name: "{{ hostvars[inventory_hostname].fqdn.split('.')[0] }}"
auth_filepaths:
users_path: /etc/proftpd/ftpd.passwd
groups_path: /etc/proftpd/ftpd.group
auth_paths:
users: /etc/proftpd/ftpd.passwd
groups: /etc/proftpd/ftpd.group
msg:
welcome: "Our head librarians Furcas and Marbas welcome you!"
vusers:
users:
webmaster:
username: webmaster
id_of: "{{ ['caddy', 'www-data'][0] }}"
gid_of: "{{ ['caddy', 'www-data'][0] }}"
id: "{{ ['caddy', 'www-data'][0] }}"
gid: "{{ ['caddy', 'www-data'][0] }}"
# @TODO create vaulted password for this ProFTPd virtual user
password: !vault |
$ANSIBLE_VAULT;1.2;AES256;vps1-webmaster
@@ -270,8 +270,8 @@ config:
services: [http,https]
smuggler:
username: smuggler
id_of: "{{ hostvars[inventory_hostname].users.ftp.username }}"
gid_of: "{{ hostvars[inventory_hostname].users.ftp.group | default(hostvars[inventory_hostname].users.ftp.username) }}"
id: "{{ hostvars[inventory_hostname].users.ftp.username }}"
gid: "{{ hostvars[inventory_hostname].users.ftp.group | default(hostvars[inventory_hostname].users.ftp.username) }}"
# @TODO create vaulted password for this ProFTPd virtual user
password: !vault |
$ANSIBLE_VAULT;1.2;AES256;vps1-smuggler
@@ -293,5 +293,5 @@ config:
phone_region: US
aria:
checksum: ~
secret: ~
api_key: ~

View File

@@ -3,7 +3,11 @@
#+language: en
* PLANNED
** TODO [#A] Write documentation on the expected conventional names to be used in the inventory file
** DONE [#A] Write documentation on the expected conventional names to be used in the inventory file
** DONE [#A] Write documentation on the expected conventional paths to be used in the inventory file
** TODO [#A] Create Python Click library/package- based CLI
** TODO [#A] Soft-code relative paths for role files/templates in Ansible tasks/plays
** TODO [#A] Soft-code project root and paths to passwords/secrets files for Ansible tasks/plays
** TODO [#A] Rewrite dot notation usage of keys for accessing values in custom dictionary variables to bracket notation usage of keys across whole project
* IN PROGRESS