Compare commits
74 Commits
0efe13e76b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d149361e60
|
||
|
|
1851b0bb04
|
||
|
|
88aac6b598
|
||
|
|
ed06773cac
|
||
|
|
0dc3edc0bc
|
||
|
|
ea7d9f7caf
|
||
|
|
ffa5408e9c
|
||
|
|
2d7f783bb9
|
||
|
|
eb437ce2e9
|
||
|
|
07ff003870
|
||
|
|
d88a9d6176
|
||
|
|
c39463f4a7
|
||
|
|
474574860a
|
||
|
|
645d815413 | ||
|
|
3c3f7cbf1d
|
||
|
|
e175e41810
|
||
|
|
3040a6203d
|
||
|
|
29bd8090be
|
||
|
|
40bbe62203
|
||
|
|
cb69a9b430
|
||
|
|
19f1681473
|
||
|
|
5e5dabff1a
|
||
|
|
d4aa128e4c
|
||
|
|
512c7a82e5
|
||
|
|
6c4c3d0794 | ||
|
|
5a75942336
|
||
|
|
dd463297dd
|
||
|
|
a1bc0ae727
|
||
|
|
4dd524a5f2
|
||
|
|
5901f69c29
|
||
|
|
73ef748c95
|
||
|
|
065e93d7c7
|
||
|
|
3f11b7c78d
|
||
|
|
64873bb846
|
||
|
|
bdc6905491
|
||
|
|
272c1342c1
|
||
|
|
db18e6ddf6
|
||
|
|
ee3cf37d2f
|
||
|
|
7d2d9081e2
|
||
|
|
870b16582c
|
||
|
|
de70d3bdd3
|
||
|
|
72395382d3 | ||
|
|
fd5cdca0e9
|
||
|
|
0a072b4c90
|
||
|
|
736277c377
|
||
|
|
b386f5e363
|
||
|
|
da694910c6
|
||
|
|
93bc285f3d
|
||
|
|
c0a798837d
|
||
|
|
856704f9d7
|
||
|
|
f74482c400
|
||
|
|
a6218cbaef
|
||
|
|
8c88322621
|
||
|
|
960f54efca
|
||
|
|
a6039e2c13
|
||
|
|
c3eed3e396
|
||
|
|
4fad50c9dc
|
||
|
|
ea2346c41b | ||
|
|
8b2390a1b7
|
||
|
|
ee8a391d0e
|
||
|
|
460e3f42ce
|
||
|
|
e06807ed37
|
||
|
|
08df87b0bf
|
||
|
|
d52f7e02a8
|
||
|
|
a387326cca
|
||
|
|
a0413c7ebc
|
||
|
|
b4f6afa628
|
||
|
|
4d3d060ec2
|
||
|
|
f97a93ee05
|
||
|
|
bbdcba0b72
|
||
|
|
4fc691d1db
|
||
|
|
8bf893ee1f
|
||
|
|
3ed2791d13
|
||
|
|
f139afe429 |
6
.bin/ansible_aliases
Executable file
6
.bin/ansible_aliases
Executable 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
187
.bin/skansible.sh
Executable 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
28
.env
Normal 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"
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
.env/
|
||||
.venv/
|
||||
*.bak
|
||||
hosts.yml
|
||||
.secrets/*
|
||||
@@ -7,4 +7,14 @@ hosts.yml
|
||||
**/*.asc
|
||||
**/*.pem
|
||||
**/*.ppk
|
||||
log.txt
|
||||
**/*.crt
|
||||
**/*.cert
|
||||
log.txt
|
||||
**/update-motd.d/*.sh
|
||||
**/vhost@vps1-sukaato.moe.conf.j2
|
||||
collections/
|
||||
motd
|
||||
banner
|
||||
.galaxy_cache/
|
||||
galaxy_token
|
||||
uv.lock
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
42
README.md
42
README.md
@@ -1,25 +1,39 @@
|
||||
# SUKAATO Ansible
|
||||
|
||||
This repository is for automating the management of the configuration of, and the provisioning of software for, my virtual private servers using [Ansible](https://www.redhat.com/en/ansible-collaborative?intcmp=7015Y000003t7aWQAQ). This repository is especially useful for setting up the virtual private server(s) that is(/are) to host and serve my website(s). It is also meant to be useful for provisioning of software and the configuration of that software for personal or household LAN computers.
|
||||
This repository is for automating the management of the configuration of, and the provisioning of software for, my virtual private servers using [Ansible](https://www.redhat.com/en/ansible-collaborative?intcmp=7015Y000003t7aWQAQ). It's main purpose is to spin up the VPSs, create initial users and groups, import SSH or GPG keys, lock down SSH access or harden SSH, and then install and configure packages available to the given package manager of the operating system. The `bootstrap` role in here serves to abstract some of these tasks for our main playbook files.
|
||||
|
||||
## Installation and Use
|
||||
## Variable Names and Their Scopes
|
||||
|
||||
All files with file extension `.example` must be converted to [YAML](https://yaml.org/) files that follow their semantics and naming (or follow the minimum bare "namespace" nesting for dictionaries or lists thereof) *prior* to executing any given [play or task](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html). For more on semantics and naming conventions see the [mini-documentation](#mini-documentation).
|
||||
To be able to make use of the Ansible playbooks, it is necessary to specify some variables in or at relevant scopes, though some may have some defaults. The relevant scopes variables are defined in, for our purposes, are:
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Keep in mind files with the `.example` extension may also be present recursively under given [role](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html) directories (i.e., under path `${SUKAATO_ANSIBLE_PROJECT}/.ansible/roles/**/**/`).
|
||||
- Ansible **inventory scope**: corresponds to variables inside per-hostname files in `group_vars` or `host_vars` directories, or the inventory file itself, i.e. `hosts.ini` or `hosts.yml`. The inventory file has some enforced naming conventions to be covered later or elsewhere.
|
||||
- Ansible **role scope**: corresponds to variables found in files inside the `defaults` / `vars` directory in a role directory, or variables found in files inside subdirectory `main` in either `defaults` or `vars` directory of that role directory. There are favored conventional directory structures within which these variables are specified in the aforementioned directories, to be covered later or elsewhere.
|
||||
|
||||
## Mini-Documentation
|
||||
Other variables that tend to have default definitions as is but that may be of interest are those found in Jinja templates of roles, in this case of the role `bootstrap`. Look through the `bootstrap` role's `templates` directory and you will discover them--most of them defined in role tasks or handlers that make reference to them. However, more information may be found elsewhere.
|
||||
|
||||
### Available Roles
|
||||
### Inventory Scope
|
||||
|
||||
To surmise, the available or planned [roles](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html) are as follows (and are all found under `${SUKAATO_ANSIBLE_PROJECT}/.ansible/roles`):
|
||||
Herein are listed the relevant variables at or in the *inventory* scope. These must be specified for a specific inventory host or group, typically in their corresponding files under `group_vars` or `host_vars`. Some variables must take in a dictionary type to be valid. To save space, there will be more detail on what keys are required or optional for such dictionaries [elsewhere](https://git.sukaato.moe/admin/skato-ansible/wiki/Inventory-Scope) and not here.
|
||||
|
||||
role name | purpose
|
||||
---|---
|
||||
lockdown | creating initial `sudo`-capable user, disabling system/SSH root login, setting up key-based SSH authentication, transferring GPG keys, configuring environment, hardening system
|
||||
bootstrap | installing programming language and server/container packages, installing extra system managers and essential utilities, configuring and running servers/services/containers
|
||||
postinstall | installing and configuring custom sets of packages, largely non-server related and not essential
|
||||
name | type | value validity rule
|
||||
---|---|---
|
||||
`fqdn` | `<str>` | fully qualified domain name
|
||||
`vps_service` | `<dict{<str>:<str\|bool\|list>}>` | valid fields providing data for spinning up new VPS
|
||||
`groups` | `<dict{$group_name:<dict>}>` | fields/keys that are group names with data configuring that group
|
||||
`users` | `<dict{$user_name:<dict>}>` | fields/keys that are user names with data configuring that user
|
||||
`keywords` | `<list[<str>]>` | strings that describe the VPS, useful for applying tags if allowed by API
|
||||
`custom_vars` | `<dict{<str>:<any>}>` | your own custom variables, though there are some reserved variable names for this namespace
|
||||
|
||||
### Role Scope
|
||||
|
||||
Herein are listed the relevant variables at or in the *role* scope. These must be specified for a set of role tasks expected to run in a playbook for the host specified for its play. Some variables must take in a dictionary type to be valid. To save space, there will be more detail on what keys are required or optional for such dictionaries [elsewhere](https://git.sukaato.moe/admin/skato-ansible/wiki/Role-Scope) and not here.
|
||||
|
||||
name | type | value validity rule
|
||||
---|---|---
|
||||
`software` | `<dict{<str>:<dict>}>` | valid fields providing data for software installations
|
||||
`config` | `<dict{$software_name:<dict>}>` | software name fields providing data for configuring that software
|
||||
|
||||
## Installation
|
||||
|
||||
> **TBC**
|
||||
> This README is yet unfinished. Check back later.
|
||||
> This README is yet unfinished and unverified. Check back later.
|
||||
|
||||
10
ansible.cfg
10
ansible.cfg
@@ -16,7 +16,7 @@
|
||||
;force_color=False
|
||||
|
||||
# (path) The default root path for Ansible config files on the controller.
|
||||
home=.ansible
|
||||
home=.
|
||||
|
||||
# (boolean) This setting allows suppressing colorizing output, which is used to give a better indication of failure and status information.
|
||||
;nocolor=False
|
||||
@@ -38,7 +38,7 @@ become_plugins=plugins/become:.ansible/plugins/become:/usr/share/ansible/plugins
|
||||
fact_caching=jsonfile
|
||||
|
||||
# (string) Defines connection or path information for the cache plugin
|
||||
fact_caching_connection=.ansible/facts
|
||||
fact_caching_connection=.facts
|
||||
|
||||
# (string) Prefix to use for cache plugin files/tables
|
||||
;fact_caching_prefix=ansible_facts
|
||||
@@ -54,7 +54,7 @@ fact_caching_connection=.ansible/facts
|
||||
|
||||
# (pathspec) Colon separated paths in which Ansible will search for collections content. Collections must be in nested *subdirectories*, not directly in these directories. For example, if ``COLLECTIONS_PATHS`` includes ``'{{ ANSIBLE_HOME ~ "/collections" }}'``, and you want to add ``my.collection`` to that directory, it must be saved as ``'{{ ANSIBLE_HOME} ~ "/collections/ansible_collections/my/collection" }}'``.
|
||||
|
||||
collections_path=.ansible/collections:collections:/usr/share/collections:/etc/ansible/collections
|
||||
collections_path=collections:.ansible/collections:/usr/share/collections:/etc/ansible/collections
|
||||
|
||||
# (boolean) A boolean to enable or disable scanning the sys.path for installed collections
|
||||
;collections_scan_sys_path=True
|
||||
@@ -169,7 +169,7 @@ local_tmp=.tmp
|
||||
;log_filter=
|
||||
|
||||
# (path) File to which Ansible will log on the controller. When empty logging is disabled.
|
||||
log_path=.ansible/log.txt
|
||||
log_path=log.txt
|
||||
|
||||
# (pathspec) Colon separated paths in which Ansible will search for Lookup Plugins.
|
||||
lookup_plugins=plugins/lookup:.ansible/lookup:/usr/share/ansible/plugins/lookup
|
||||
@@ -223,7 +223,7 @@ netconf_plugins=plugins/netconf:.ansible/netconf:/usr/share/ansible/plugins/netc
|
||||
;remote_user=
|
||||
|
||||
# (pathspec) Colon separated paths in which Ansible will search for Roles.
|
||||
roles_path=.ansible/roles:roles:/usr/share/ansible/roles:/etc/ansible/roles
|
||||
roles_path=roles:.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
|
||||
|
||||
# (string) Set the main callback used to display Ansible output. You can only have one at a time.
|
||||
# You can have many other callbacks, but just one can be in charge of stdout.
|
||||
|
||||
@@ -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
|
||||
@@ -39,6 +39,10 @@ vps_service:
|
||||
- ~/.ssh/id_ecdsa-sha2_sukaato_yubikey.ppk
|
||||
- ~/.ssh/id_ecdsa-sha2_sukaato_miniyubikey.ppk
|
||||
ssh_private_key_path_pref: 0
|
||||
ssh_motd_script_basenames:
|
||||
- 00-logo.sh
|
||||
- 01-server.sh
|
||||
- 02-info.sh
|
||||
keywords:
|
||||
- social media
|
||||
- internet
|
||||
@@ -51,11 +55,13 @@ 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:
|
||||
username: senpai
|
||||
id: 1000
|
||||
password: !vault |
|
||||
$ANSIBLE_VAULT;1.2;AES256;vps1-senpai
|
||||
62626662666239376237616464626630393562373130623934653764333139346337313539613863
|
||||
@@ -75,9 +81,9 @@ users:
|
||||
groups:
|
||||
- sudo
|
||||
- "{{ groups.remote.group_name }}"
|
||||
services: []
|
||||
ssh_authorized_keys: "{{ custom_vars.generality.ssh_authorized_keys }}"
|
||||
ssh_private_key_paths: "{{ custom_vars.generality.ssh_private_key_paths }}"
|
||||
services: [sshd]
|
||||
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
|
||||
@@ -113,6 +119,7 @@ users:
|
||||
email: ajt95@prole.biz
|
||||
ftp:
|
||||
username: ftp
|
||||
id: 999
|
||||
password: ~
|
||||
shell: /sbin/nologin
|
||||
home: /srv/ftp
|
||||
@@ -121,9 +128,9 @@ users:
|
||||
group: ~
|
||||
groups:
|
||||
- "{{ groups.remote.group_name }}"
|
||||
services: [proftpd,sftp]
|
||||
ssh_authorized_keys: "{{ custom_vars.generality.ssh_authorized_keys }}"
|
||||
ssh_private_key_paths: "{{ custom_vars.generality.ssh_private_key_paths }}"
|
||||
services: [proftpd,sftp,ftps]
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
9
playbooks/soft-init.yml
Normal file
9
playbooks/soft-init.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: Configure the rest of the system for the administrative user(s)
|
||||
hosts: vps1
|
||||
# remote_user: # @NOTE can be uncommented to insert known administrative user
|
||||
# @NOTE below can be uncommented to insert known administrative user
|
||||
# vars:
|
||||
# ansible_user: root
|
||||
tasks:
|
||||
|
||||
12
pyproject.toml
Normal file
12
pyproject.toml
Normal 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",
|
||||
]
|
||||
16
roles/bootstrap/files/fail2ban/filter.d/custom_proftpd.conf
Normal file
16
roles/bootstrap/files/fail2ban/filter.d/custom_proftpd.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
# fail2ban filter for the ProFTPD FTP daemon
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_daemon = proftpd
|
||||
|
||||
failregex = \(\S+\[<HOST>\]\)[: -]+ USER \S+: no such user found from \S+ \[[0-9.]+\] to \S+:\S+\s*$
|
||||
\(\S+\[<HOST>\]\)[: -]+ USER \S+ \(Login failed\):.*\s+$
|
||||
\(\S+\[<HOST>\]\)[: -]+ Maximum login attempts \([0-9]+\) exceeded, connection refused.*\s+$
|
||||
\(\S+\[<HOST>\]\)[: -]+ SECURITY VIOLATION: \S+ login attempted\.\s+$
|
||||
\(\S+\[<HOST>\]\)[: -]+ Maximum login attempts \(\d+\) exceeded\s+$
|
||||
|
||||
ignoreregex =
|
||||
7
roles/bootstrap/files/fail2ban/jail.d/proftpd.local
Normal file
7
roles/bootstrap/files/fail2ban/jail.d/proftpd.local
Normal file
@@ -0,0 +1,7 @@
|
||||
[proftpd]
|
||||
|
||||
enabled = true
|
||||
port = 990
|
||||
filter = custom_proftpd
|
||||
logpath = /var/log/proftpd.log
|
||||
maxretry = 6
|
||||
35
roles/bootstrap/files/fail2ban/jail.d/sshd.local
Normal file
35
roles/bootstrap/files/fail2ban/jail.d/sshd.local
Normal file
@@ -0,0 +1,35 @@
|
||||
[sshd]
|
||||
|
||||
# ==========================
|
||||
# SSH Jail Configuration
|
||||
# ==========================
|
||||
|
||||
# Enable the SSH jail to monitor and protect against brute-force attacks.
|
||||
enabled = true
|
||||
|
||||
# Port Fail2Ban should monitor for SSH connections.
|
||||
# If you run SSH on a custom port, replace 'ssh' with the actual port number (e.g., 2222).
|
||||
port = ssh
|
||||
|
||||
# Filter definition to use.
|
||||
# 'sshd' refers to the default filter that matches common SSH authentication failures.
|
||||
filter = sshd
|
||||
|
||||
# Log file location.
|
||||
# '%(sshd_log)s' uses the default value set by the system, typically /var/log/auth.log or journalctl.
|
||||
logpath = %(sshd_log)s
|
||||
|
||||
# Backend for reading logs.
|
||||
# 'systemd' is recommended if your system uses journalctl for logging.
|
||||
backend = systemd
|
||||
|
||||
# ==========================
|
||||
# SSH-Specific Overrides
|
||||
# ==========================
|
||||
|
||||
# Time window to evaluate failed login attempts.
|
||||
# If 'maxretry' failures occur within this time, the IP will be banned.
|
||||
findtime = 5m
|
||||
|
||||
# Number of failed attempts allowed before triggering a ban.
|
||||
maxretry = 4
|
||||
49
roles/bootstrap/handlers/aria2.yml
Normal file
49
roles/bootstrap/handlers/aria2.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for bootstrap
|
||||
- name: Configure Aria2
|
||||
listen: aria
|
||||
when: ansible_facts['user_id'] in hostvars[inventory_hostname].users.keys()
|
||||
block:
|
||||
- name: Create Aria2 user configuration directory
|
||||
block:
|
||||
- name: Create configuration directory
|
||||
ansible.builtin.file:
|
||||
force: false
|
||||
group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}"
|
||||
owner: "{{ ansible_facts['user_id'] }}"
|
||||
path: "{{ ansible_facts['user_dir'] }}/.config"
|
||||
state: directory
|
||||
- name: Create configuration directory
|
||||
when: ansible_facts['user_id'] in hostvars[inventory_hostname].users.keys()
|
||||
ansible.builtin.file:
|
||||
force: false
|
||||
group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}"
|
||||
owner: "{{ ansible_facts['user_id'] }}"
|
||||
path: "{{ ansible_facts['user_dir'] }}/.config/aria2"
|
||||
state: directory
|
||||
- name: Create Aria2 configuration
|
||||
ansible.builtin.template:
|
||||
backup: true
|
||||
dest: "{{ ansible_facts['user_dir'] }}/.config/aria2/aria2.conf"
|
||||
force: true
|
||||
group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}"
|
||||
owner: "{{ ansible_facts['user_id'] }}"
|
||||
src: aria2/aria2.conf.j2
|
||||
# validate: string
|
||||
- name: Create Aria2 SystemD user unit service
|
||||
ansible.builtin.copy:
|
||||
backup: true
|
||||
dest: "{{ ansible_facts['user_dir'] }}/.config/systemd/user/aria2cd.service"
|
||||
force: true
|
||||
group: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].group | default(ansible_facts['user_id']) }}"
|
||||
owner: "{{ ansible_facts['user_id'] }}"
|
||||
src: systemd/user/aria2cd.service.j2
|
||||
# validate: string
|
||||
- name: Start Aria2 SystemD user unit service
|
||||
ansible.builtin.systemd_service:
|
||||
daemon_reload: true
|
||||
enabled: true
|
||||
name: aria2cd
|
||||
scope: user
|
||||
state: started
|
||||
@@ -1,3 +0,0 @@
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for bootstrap
|
||||
@@ -304,3 +304,11 @@
|
||||
scope: global
|
||||
state: present
|
||||
value: "{{ hostvars[inventory_hostname].users[ansible_facts['user_id']].git_profile.email }}"
|
||||
- name: Set default initial branch name
|
||||
become: true
|
||||
community.general.git_config:
|
||||
add_mode: replace-all
|
||||
name: init.defaultBranch
|
||||
scope: system
|
||||
state: present
|
||||
value: main
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for bootstrap
|
||||
# @NOTE below for packages
|
||||
- name: Postinstall set-up of git
|
||||
ansible.builtin.import_tasks:
|
||||
file: git.yml
|
||||
- name: Postinstall set-up of ProFTPd
|
||||
ansible.builtin.import_tasks:
|
||||
file: proftpd.yml
|
||||
- name: Postinstall set-up of ProFTPd
|
||||
ansible.builtin.import_tasks:
|
||||
file: proftpd.yml
|
||||
# @NOTE below for snaps
|
||||
- name: Postinstall set-up of snapd
|
||||
ansible.builtin.import_tasks:
|
||||
file: snapd.yml
|
||||
- name: Postinstall set-up of Nextcloud snap
|
||||
ansible.builtin.import_tasks:
|
||||
file: nextcloud.yml
|
||||
|
||||
105
roles/bootstrap/handlers/nextcloud.yml
Normal file
105
roles/bootstrap/handlers/nextcloud.yml
Normal file
@@ -0,0 +1,105 @@
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for bootstrap
|
||||
- name: Configure Nextcloud snap
|
||||
become: true
|
||||
listen: nextcloud
|
||||
block:
|
||||
- name: Enable monitoring of network hardware
|
||||
ansible.builtin.command:
|
||||
cmd: "snap connect nextcloud:network-observe"
|
||||
- name: Begin manual installation
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /snap/bin/nextcloud.manual-install
|
||||
- "{{ config.nextcloud.users.admin.username }}"
|
||||
- "{{ config.nextcloud.users.admin.password }}"
|
||||
# @TODO see if setting below is necessary given use of reverse proxy
|
||||
- name: Set trusted domains
|
||||
block:
|
||||
- name: Set FQDN as trusted domain
|
||||
ansible.builtin.command:
|
||||
cmd: "/snap/bin//snap/bin/nextcloud.occ config:system:set trusted_domains 1 --value='cloud.{{ hostvars[inventory_hostname].fqdn }}'"
|
||||
# @TODO configure perhaps for trusted (reverse) proxy instead of above
|
||||
- name: Set trusted reverse proxy addresses
|
||||
block:
|
||||
- name: Set trusted reverse proxy IPv4 address based on hostname
|
||||
# @TODO create config.trusted_revproxy_ips data structure in bootstrap role's vars dir--may include loopback addresses
|
||||
when: config.trusted_revproxy_ips.ipv4 is None or len(config.trusted_revproxy_ips.ipv4) < 1
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /snap/bin/nextcloud.occ
|
||||
- "config:system:set"
|
||||
- trusted_proxies 0
|
||||
- "--value=$(hostname -I | awk -F ' ' '{ print $1 }')"
|
||||
- name: Set trusted reverse proxy IPv4 address
|
||||
when: config.trusted_revproxy_ips.ipv4 is not None and len(config.trusted_revproxy_ips.ipv4) > 0
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /snap/bin/nextcloud.occ
|
||||
- "config:system:set"
|
||||
- "trusted_proxies {{ idx }}"
|
||||
- "--value={{ item }}"
|
||||
loop: "{{ config.trusted_revproxy_ips.ipv4 }}"
|
||||
loop_control:
|
||||
index_var: idx
|
||||
- name: Set trusted reverse proxy IPv6 address based on hostname
|
||||
when: config.trusted_revproxy_ips.ipv6 is None or len(config.trusted_revproxy_ips.ipv6) < 1
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /snap/bin/nextcloud.occ
|
||||
- "config:system:set"
|
||||
- trusted_proxies 1
|
||||
- --value=$(hostname -I | awk -F ' ' '{ print $2 }')
|
||||
- name: Set trusted reverse proxy IPv6 address
|
||||
when: config.trusted_revproxy_ips.ipv6 is not None and len(config.trusted_revproxy_ips.ipv6) > 0
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /snap/bin/nextcloud.occ
|
||||
- "config:system:set"
|
||||
- "trusted_proxies {{ idx }}"
|
||||
- "--value={{ item }}"
|
||||
loop: "{{ config.trusted_revproxy_ips.ipv6 }}"
|
||||
loop_control:
|
||||
index_var: idx
|
||||
# @TODO create task based on shell command `sudo /snap/bin/nextcloud.occ config:system:set default_phone_region --value="US"`
|
||||
- name: Set default phone region
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /snap/bin/nextcloud.occ
|
||||
- "config:system:set"
|
||||
- default_phone_region
|
||||
- "--value={{ config.nextcloud.phone_region }}"
|
||||
# @TODO create task based on shell command:
|
||||
# `sudo /snap/bin/nextcloud.occ config:system:set overwrite.cli.url --value="https://cloud.{{ fqdn }}"` for Caddy task
|
||||
- name: Set overwrite CLI URL
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /snap/bin/nextcloud.occ
|
||||
- "config:system:set"
|
||||
- overwrite.cli.url
|
||||
- "--value=cloud.{{ hostvars[inventory_hostname].fqdn }}"
|
||||
# @TODO create task based on shell command `sudo /snap/bin/nextcloud.occ config:system:set overwriteprotocol --value="https"` for Caddy task
|
||||
- name: Overwrite protocol
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /snap/bin/nextcloud.occ
|
||||
- "config:system:set"
|
||||
- overwriteprotocol
|
||||
- --value="https"
|
||||
# @TODO create system-level bash alias for `/snap/bin/nextcloud.occ` command
|
||||
- name: Get Nextcloud snap binaries
|
||||
ansible.builtin.find:
|
||||
paths:
|
||||
- /snap/bin
|
||||
patterns:
|
||||
- nextcloud\..*
|
||||
recurse: false
|
||||
use_regex: true
|
||||
register: nextcloud_snap_binaries
|
||||
- name: Create symbolic links for Nextcloud snap binaries
|
||||
ansible.builtin.file:
|
||||
dest: "/usr/sbin/{{ item.path | basename }}"
|
||||
src: "{{ item.path }}"
|
||||
state: link
|
||||
loop: "{{ nextcloud_snap_binaries.files }}"
|
||||
157
roles/bootstrap/handlers/proftpd.yml
Normal file
157
roles/bootstrap/handlers/proftpd.yml
Normal file
@@ -0,0 +1,157 @@
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for bootstrap
|
||||
- name: Configure ProFTPd server
|
||||
listen: proftpd
|
||||
become: true
|
||||
block:
|
||||
- name: Create ProFTPd non-web user subdirectories
|
||||
when: "'ftps' in item[0]['services'] and not 'caddy' in item[1]['services'] and not 'httpd' in item[1]['services'] and not 'www-data' in item[1]['services'] and not 'http' in item[1]['services'] and not 'https' in item[1]['services']"
|
||||
ansible.builtin.file:
|
||||
# follow: true
|
||||
force: true
|
||||
owner: "{{ item[0]['username'] }}"
|
||||
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']['users'].values()) }}"
|
||||
- name: Create ProFTPd FTP public directory for anonymous logins
|
||||
when: "'ftps' in item.value['services']"
|
||||
ansible.builtin.file:
|
||||
# follow: true
|
||||
force: true
|
||||
owner: "{{ item.value['username'] }}"
|
||||
group: "{{ item.value['group'] | default(item.value['username']) }}"
|
||||
path: "{{ item.value['home'] | default('/home/' ~ item.value['username']) }}/public"
|
||||
state: directory
|
||||
loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname]['users']) }}"
|
||||
- name: Configure ProFTPd main control server
|
||||
ansible.builtin.template:
|
||||
backup: true
|
||||
comment_end_string: "#}"
|
||||
comment_start_string: "{#"
|
||||
dest: /etc/proftpd/proftpd.conf
|
||||
follow: true
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
src: proftpd/proftpd.conf.j2
|
||||
validate: proftpd --configtest
|
||||
vars:
|
||||
ftp_server_name: init
|
||||
max_conns: 30
|
||||
- name: Configure ProFTPd global settings
|
||||
ansible.builtin.template:
|
||||
backup: true
|
||||
comment_end_string: "#}"
|
||||
comment_start_string: "{#"
|
||||
dest: /etc/proftpd/conf.d/global.conf
|
||||
follow: true
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
src: proftpd/conf.d/global.conf.j2
|
||||
validate: proftpd --configtest
|
||||
vars:
|
||||
pasv_ports: "49152 65534"
|
||||
allow_symlinks: false
|
||||
- name: Add virtual users to ProFTPd
|
||||
block:
|
||||
- name: Create virtual user authentication files
|
||||
ansible.builtin.file:
|
||||
force: true
|
||||
group: root
|
||||
mode: "0640"
|
||||
owner: root
|
||||
path: "{{ item.value }}"
|
||||
state: touch
|
||||
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:
|
||||
argv:
|
||||
- ftpasswd
|
||||
- --passwd
|
||||
- "--name={{ item.value['username'] }}"
|
||||
- "--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_paths']['users'] }}
|
||||
- --stdin
|
||||
stdin: "{{ item.value['password'] }}"
|
||||
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:
|
||||
argv:
|
||||
- ftpasswd
|
||||
- --group
|
||||
- "--name={{ item.value['username'] }}"
|
||||
- "--gid=$(id -g {{ item.value['gid'] }})"
|
||||
- "--member={{ item.value['username'] }}"
|
||||
- --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
|
||||
ansible.builtin.template:
|
||||
backup: true
|
||||
comment_end_string: "#}"
|
||||
comment_start_string: "{#"
|
||||
dest: "/etc/proftpd/conf.d/{{ config['proftpd']['name'].lowercase() }}.conf"
|
||||
follow: true
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
src: "proftpd/conf.d/vhost@vps1-{{ hostvars[inventory_hostname].fqdn }}.conf.j2"
|
||||
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']['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']['users']['smuggler']['username'] }}"
|
||||
- name: Set ProFTPd jail in fail2ban
|
||||
block:
|
||||
- name: Create fail2ban system configuration directory
|
||||
ansible.builtin.file:
|
||||
force: false
|
||||
group: root
|
||||
mode: "0755"
|
||||
owner: root
|
||||
path: /etc/fail2ban
|
||||
state: directory
|
||||
- name: Create fail2ban filters system configuration directory
|
||||
ansible.builtin.file:
|
||||
force: false
|
||||
group: root
|
||||
mode: "0755"
|
||||
owner: root
|
||||
path: /etc/fail2ban/filter.d
|
||||
state: directory
|
||||
- name: Create fail2ban filter system configuration
|
||||
ansible.builtin.copy:
|
||||
backup: true
|
||||
dest: /etc/fail2ban/filter.d/custom_proftpd.conf
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
src: fail2ban/filter.d/custom_proftpd.conf
|
||||
# validate: string
|
||||
- name: Create fail2ban jails system configuration directory
|
||||
ansible.builtin.file:
|
||||
force: false
|
||||
group: root
|
||||
mode: "0755"
|
||||
owner: root
|
||||
path: /etc/fail2ban/jail.d
|
||||
state: directory
|
||||
- name: Create fail2ban jail system configuration
|
||||
ansible.builtin.copy:
|
||||
backup: true
|
||||
dest: /etc/fail2ban/jail.d/proftpd.local
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
src: fail2ban/jail.d/proftpd.local
|
||||
# validate: string
|
||||
24
roles/bootstrap/handlers/rsyncd.yml
Normal file
24
roles/bootstrap/handlers/rsyncd.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for bootstrap
|
||||
- name: Configure RSyncD
|
||||
listen: rsync
|
||||
become: true
|
||||
block:
|
||||
# @TODO further construct the following commented task
|
||||
# - name: Add directories to be published by RSyncD
|
||||
- name: Create RSyncD configuration
|
||||
ansible.builtin.template:
|
||||
backup: true
|
||||
dest: /etc/rsyncd.conf
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
src: rsyncd.conf.j2
|
||||
# validate: string
|
||||
- name: Start and enable RSyncD SystemD system unit service
|
||||
ansible.builtin.systemd_service:
|
||||
enabled: true
|
||||
name: rsync
|
||||
scope: system
|
||||
state: started
|
||||
17
roles/bootstrap/handlers/snapd.yml
Normal file
17
roles/bootstrap/handlers/snapd.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# handlers file for bootstrap
|
||||
- name: Install all snapd applications
|
||||
become: true
|
||||
listen: snapd
|
||||
block:
|
||||
- name: Install snaps
|
||||
community.general.snap:
|
||||
channel: "{{ item.value['channel'] | default('latest/stable') }}"
|
||||
name:
|
||||
- "{{ item.value['name'] }}"
|
||||
# @TODO test the below list extend method for list of lists
|
||||
options: "{{ item.value['opts'] }}"
|
||||
state: present
|
||||
notify: "{{ item.key }}"
|
||||
loop: "{{ lookup('ansible.builtin.dict', software.snaps) }}"
|
||||
@@ -1,12 +1,9 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
# SPDX-License-Identifier: MIT-0
|
||||
galaxy_info:
|
||||
author: your name
|
||||
description: your role description
|
||||
company: your company (optional)
|
||||
|
||||
# If the issue tracker for your role is not on github, uncomment the
|
||||
# next line and provide a value
|
||||
# issue_tracker_url: http://example.com/issue/tracker
|
||||
author: Alex Tavarez
|
||||
description: A role that aids in the deployment and bootstrapping of a new VPS.
|
||||
company: SUKAATO
|
||||
issue_tracker_url: https://git.sukaato.moe/admin/skato-ansible/issues
|
||||
|
||||
# Choose a valid license ID from https://spdx.org - some suggested licenses:
|
||||
# - BSD-3-Clause (default)
|
||||
@@ -16,20 +13,13 @@ galaxy_info:
|
||||
# - Apache-2.0
|
||||
# - CC-BY-4.0
|
||||
license: license (GPL-2.0-or-later, MIT, etc)
|
||||
min_ansible_version: "2.1"
|
||||
galaxy_tags:
|
||||
- sukaato
|
||||
- vps
|
||||
- server
|
||||
- web
|
||||
|
||||
min_ansible_version: 2.1
|
||||
|
||||
# If this a Container Enabled role, provide the minimum Ansible Container version.
|
||||
# min_ansible_container_version:
|
||||
|
||||
galaxy_tags: []
|
||||
# List tags for your role here, one per line. A tag is a keyword that describes
|
||||
# and categorizes the role. Users find roles by searching for tags. Be sure to
|
||||
# remove the '[]' above, if you add tags to this list.
|
||||
#
|
||||
# NOTE: A tag is limited to a single word comprised of alphanumeric characters.
|
||||
# Maximum 20 tags per role.
|
||||
|
||||
dependencies: []
|
||||
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
|
||||
# if you add dependencies to this list.
|
||||
dependencies:
|
||||
- community.general
|
||||
# - containers.podman
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# tasks file for bootstrap
|
||||
- name: Create directory for MOTD update scripts
|
||||
ansible.builtin.file:
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
path: /etc/update-motd.d
|
||||
state: directory
|
||||
- name: Create MOTD update scripts
|
||||
ansible.builtin.copy:
|
||||
force: true
|
||||
backup: true
|
||||
group: root
|
||||
mode: "0744"
|
||||
owner: root
|
||||
dest: "/etc/update-motd.d/{{ item }}"
|
||||
src: "update-motd.d/{{ item }}"
|
||||
state: present
|
||||
loop: "{{ hostvars[inventory_hostname].vps_service.ssh_motd_script_basenames }}"
|
||||
- name: Create hidden SSH directories under users' home directories
|
||||
when: hostvars[inventory_hostname].groups.remote.group_name in item.value.groups
|
||||
ansible.builtin.file:
|
||||
@@ -17,7 +35,7 @@
|
||||
when: hostvars[inventory_hostname].groups.remote.group_name in item.value.groups and item.value.ssh_authorized_keys is not None and len(item.value.ssh_authorized_keys) > 0
|
||||
ansible.builtin.copy:
|
||||
backup: true
|
||||
content: "{{ item.value.ssh_authorized_keys.join('\n') }}"
|
||||
content: "{{ '\n'.join(item.value.ssh_authorized_keys) }}"
|
||||
dest: "{{ item.value.home | default('/home/' ~ item.value.username) }}/.ssh/authorized_keys"
|
||||
# follow: true
|
||||
force: true
|
||||
@@ -29,21 +47,29 @@
|
||||
- ensure_files
|
||||
- name: Harden SSH security
|
||||
block:
|
||||
- name: Create public subdirectory for SSH's SFTP-exclusive user's chroot
|
||||
when: "'sftp' in item.value.services"
|
||||
ansible.builtin.file:
|
||||
group: "{{ item.value.group | default(item.value.username) }}"
|
||||
owner: "{{ item.value.username }}"
|
||||
path: "{{ item.value.home | default('/home/' ~ item.value.username) }}/public"
|
||||
state: directory
|
||||
loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}"
|
||||
- name: Set users in group ftp to only be usable with SSH's SFTP service
|
||||
when: "'sftp' in item.value.services"
|
||||
ansible.builtin.blockinfile:
|
||||
backup: true
|
||||
block: |
|
||||
block: |2
|
||||
Match User {{ item.value.username }}
|
||||
ForceCommand internal-sftp -d /public
|
||||
AuthorizedKeysFile {{ item.value.home | default('/home/' ~ item.value.username) }}/.ssh/authorized_keys
|
||||
|
||||
Match Group {{ item.value.group | default(item.value.username) }}
|
||||
ForceCommand internal-sftp -d /%u
|
||||
ChrootDirectory {{ item.value.home | default('/home/' ~ item.value.username) }}
|
||||
AllowAgentForwarding no
|
||||
AllowTcpForwarding no
|
||||
X11Forwarding no
|
||||
|
||||
Match User {{ item.value.username }}
|
||||
ForceCommand internal-sftp -d /public
|
||||
AuthorizedKeysFile {{ item.value.home | default('/home/' ~ item.value.username) }}/.ssh/authorized_keys
|
||||
create: true
|
||||
group: root
|
||||
insertafter: EOF
|
||||
@@ -73,7 +99,7 @@
|
||||
vars:
|
||||
empty_auth_used: false
|
||||
pass_auth_used: false
|
||||
pam_auth_used: false
|
||||
pam_auth_used: true
|
||||
key_auth_used: true
|
||||
tags:
|
||||
- ssh_auth_step
|
||||
|
||||
39
roles/bootstrap/tasks/configure_ssl@linux.yml
Normal file
39
roles/bootstrap/tasks/configure_ssl@linux.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
#SPDX-License-Identifier: MIT-0
|
||||
---
|
||||
# tasks file for bootstrap
|
||||
- name: Provide requisite SSL signed certificate for FQDN
|
||||
ansible.builtin.copy:
|
||||
backup: true
|
||||
checksum: string
|
||||
dest: "/usr/local/share/ca-certificates/{{ hostvars[inventory_hostname].fqdn }}.crt"
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
src: "ca-certificates/{{ hostvars[inventory_hostname].fqdn }}.crt"
|
||||
# validate: string
|
||||
- name: Provide requisite SSL private key for FQDN
|
||||
ansible.builtin.copy:
|
||||
backup: true
|
||||
dest: "/usr/local/share/ca-certificates/{{ hostvars[inventory_hostname].fqdn }}.key"
|
||||
force: true
|
||||
group: root
|
||||
mode: "0600"
|
||||
owner: root
|
||||
src: "ca-certificates/{{ hostvars[inventory_hostname].fqdn }}.key"
|
||||
# validate: string
|
||||
- name: Provide requisite SSL public key for FQDN
|
||||
ansible.builtin.copy:
|
||||
backup: true
|
||||
checksum: string
|
||||
dest: "/usr/local/share/ca-certificates/{{ hostvars[inventory_hostname].fqdn }}.pem"
|
||||
force: true
|
||||
group: root
|
||||
owner: root
|
||||
src: "ca-certificates/{{ hostvars[inventory_hostname].fqdn }}.pem"
|
||||
# validate: string
|
||||
- name: Update system registration of SSL certificates
|
||||
ansible.builtin.command:
|
||||
cmd: update-ca-certificates
|
||||
creates: "/etc/ssl/certs/{{ hostvars[inventory_hostname].fqdn }}.pem"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
ansible.builtin.group:
|
||||
name: "{{ item.value.group_name }}"
|
||||
state: present
|
||||
system: "{{ 'true' if item.value.type == 'system' else 'false' }}"
|
||||
system: "{{ item.value.type == 'system' }}"
|
||||
loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].groups) }}"
|
||||
- name: Create users
|
||||
block:
|
||||
@@ -20,10 +20,11 @@
|
||||
ssh_key_type: "ed25519"
|
||||
group: "{{ item.value.group | default(item.value.username) }}"
|
||||
name: "{{ item.value.username }}"
|
||||
uid: "{{ item.value.id }}"
|
||||
shell: "{{ item.value.shell }}"
|
||||
password: "{{ item.value.password }}"
|
||||
state: present
|
||||
system: "{{ 'true' if item.value.type == 'system' else 'false' }}"
|
||||
system: "{{ item.value.type == 'system' }}"
|
||||
update_password: always
|
||||
loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}"
|
||||
- name: Create regular users
|
||||
@@ -35,10 +36,11 @@
|
||||
generate_ssh_key: true
|
||||
group: "{{ item.value.group | default(item.value.username) }}"
|
||||
name: "{{ item.value.username }}"
|
||||
uid: "{{ item.value.id }}"
|
||||
shell: "{{ item.value.shell }}"
|
||||
password: "{{ item.value.password }}"
|
||||
state: present
|
||||
system: "{{ 'true' if item.value.type == 'system' else 'false' }}"
|
||||
system: "{{ item.value.type == 'system' }}"
|
||||
update_password: always
|
||||
loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}"
|
||||
- name: Create users for managing data related to services
|
||||
@@ -49,9 +51,10 @@
|
||||
home: "{{ item.value.home | default('/home/' ~ item.value.username) }}"
|
||||
group: "{{ item.value.group | default(item.value.username) }}"
|
||||
name: "{{ item.value.username }}"
|
||||
uid: "{{ item.value.id }}"
|
||||
shell: "{{ item.value.shell }}"
|
||||
state: present
|
||||
system: "{{ 'true' if item.value.type == 'system' else 'false' }}"
|
||||
system: "{{ item.value.type == 'system' }}"
|
||||
loop: "{{ lookup('ansible.builtin.dict', hostvars[inventory_hostname].users) }}"
|
||||
- name: Adjust users' groups
|
||||
when: item.value.groups is not None and len(item.value.groups) > 0
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
- name: Populate system with groups and user accounts
|
||||
ansible.builtin.import_tasks:
|
||||
file: "create_users@{{ ansible_facts['system'].lowercase() }}.yml"
|
||||
- name: Provide SSL certificate files
|
||||
ansible.builtin.import_tasks:
|
||||
file: "configure_ssl@{{ ansible_facts['system'].lowercase() }}.yml"
|
||||
- name: Configure SSH for root
|
||||
ansible.builtin.import_tasks:
|
||||
file: "configure_ssh@{{ ansible_facts['system'].lowercase() }}.yml"
|
||||
@@ -21,7 +21,7 @@
|
||||
use: "{{ ansible_facts['pkg_mgr'] }}"
|
||||
state: present
|
||||
# @TODO create handler to notify to for configuring neovim
|
||||
# notify: neovim
|
||||
# notify: neovim
|
||||
- name: Install kitty-terminfo for SSH client xterm-kitty compatibility
|
||||
become: true
|
||||
block:
|
||||
@@ -42,6 +42,7 @@
|
||||
name: snapd
|
||||
use: "{{ ansible_facts['pkg_mgr'] }}"
|
||||
state: present
|
||||
notify: snapd
|
||||
- name: Install flatpak
|
||||
when: "ansible_facts['pkg_mgr'] == 'apt'"
|
||||
block:
|
||||
@@ -74,7 +75,7 @@
|
||||
name: git
|
||||
use: "{{ ansible_facts['pkg_mgr'] }}"
|
||||
state: present
|
||||
notify: git
|
||||
notify: git
|
||||
- name: Install packages
|
||||
when: ansible_facts['pkg_mgr'] in item.value.name
|
||||
ansible.builtin.package:
|
||||
|
||||
83
roles/bootstrap/templates/aria2/aria2.conf.j2
Normal file
83
roles/bootstrap/templates/aria2/aria2.conf.j2
Normal file
@@ -0,0 +1,83 @@
|
||||
# Global settings
|
||||
continue=true
|
||||
# check-integrity=true
|
||||
daemon=true
|
||||
human-readable=true
|
||||
dir=~/downloads
|
||||
file-allocation=falloc
|
||||
log-level=warn
|
||||
max-concurrent-downloads=3
|
||||
max-overall-download-limit=0
|
||||
|
||||
# RPC settings
|
||||
enable-rpc=true
|
||||
rpc-allow-origin-all=true
|
||||
rpc-max-request-size=10M
|
||||
rpc-listen-all=true
|
||||
rpc-listen-port=6800
|
||||
rpc-secret={{ config['aria']['api_key'] }}
|
||||
# rpc-certificate=
|
||||
# rpc-private-key=
|
||||
# rpc-secure=true
|
||||
|
||||
# HTTP/FTP/SFTP settings
|
||||
connect-timeout=120
|
||||
timeout=90
|
||||
server-stat-of=~/.config/aria2/dl.log
|
||||
server-stat-if=~/.config/aria2/dl.log
|
||||
server-stat-timeout=86400
|
||||
# checksum=sha-256={{ config.aria.checksum }}
|
||||
max-connection-per-server=5
|
||||
max-tries=10
|
||||
max-file-not-found=7
|
||||
min-split-size=100M
|
||||
split=7
|
||||
retry-wait=10
|
||||
netrc-path=~/.netrc
|
||||
reuse-uri=true
|
||||
uri-selector=feedback
|
||||
seed-ratio=1.5
|
||||
seed-time=75
|
||||
|
||||
# HTTP settings
|
||||
http-accept-gzip=true
|
||||
http-auth-challenge=true
|
||||
http-no-cache=true
|
||||
enable-http-pipelining=true
|
||||
enable-http-keep-alive=true
|
||||
save-cookies=~/.config/aria2/cookie.txt
|
||||
load-cookies=~/.config/aria2/cookie.txt
|
||||
# user-agent=Mozilla/5.0
|
||||
|
||||
# FTP/SFTP settings
|
||||
ftp-pasv=true
|
||||
ftp-type=binary
|
||||
ftp-reuse-connection=true
|
||||
# ssh-host-key-md=sha-256=
|
||||
|
||||
# Bittorrent settings
|
||||
listen-port=6881-6999
|
||||
# bt-hash-check-seed=true
|
||||
# bt-force-encryption=true
|
||||
bt-save-metadata=true
|
||||
bt-load-saved-metadata=true
|
||||
bt-max-open-files=50
|
||||
bt-max-peers=150
|
||||
bt-stop-timeout=7200
|
||||
|
||||
bt-tracker=udp://93.158.213.92:1337/announce,udp://23.134.88.9:6969/announce,udp://23.134.88.9:1337/announce,udp://185.243.218.213:80/announce,udp://89.234.156.205:451/announce,udp://44.30.4.4:6969/announce,udp://23.175.184.30:23333/announce,udp://51.222.82.36:6969/announce,udp://211.75.205.189:80/announce,udp://77.91.85.95:6969/announce,udp://45.13.119.213:6969/announce,udp://43.154.112.29:17272/announce,udp://209.141.59.25:6969/announce,udp://5.255.124.190:6969/announce,udp://152.53.152.105:1984/announce,udp://109.201.134.183:80/announce,udp://111.90.151.241:6969/announce,udp://152.53.152.105:54123/announce,udp://189.69.171.209:6969/announce,udp://151.243.109.110:6969/announce
|
||||
|
||||
bt-tracker-connect-timeout=120
|
||||
bt-tracker-timeout=333
|
||||
bt-tracker-interval=0
|
||||
enable-dht=true
|
||||
dht-listen-port=6881-6999
|
||||
dht-message-timeout=15
|
||||
enable-peer-exchange=true
|
||||
follow-torrent=mem
|
||||
|
||||
# Metalink settings
|
||||
follow-metalink=mem
|
||||
metalink-language=en,es,ja
|
||||
metalink-location=jp,us,ch
|
||||
# metalink-os=linux
|
||||
31
roles/bootstrap/templates/proftpd/conf.d/global.conf.j2
Normal file
31
roles/bootstrap/templates/proftpd/conf.d/global.conf.j2
Normal file
@@ -0,0 +1,31 @@
|
||||
<Global>
|
||||
# PassivePorts {{ pasv_ports }}
|
||||
RequireValidShell off
|
||||
{% if allow_symlinks %}
|
||||
ShowSymlinks on
|
||||
{% else %}
|
||||
ShowSymlinks off
|
||||
{% endif %}
|
||||
AllowRetrieveRestart on
|
||||
HiddenStores .%P- .frag
|
||||
|
||||
DisplayLogin /etc/proftpd/WELCOME.txt
|
||||
DisplayChdir .README.md true
|
||||
DisplayConnect /etc/proftpd/BANNER.txt
|
||||
DisplayFileTransfer /etc/proftpd/SUCCESS.txt
|
||||
DisplayReadme /etc/proftpd/ANNOUNCE.md
|
||||
DisplayQuit /etc/proftpd/BYE.txt
|
||||
|
||||
TimeoutNoTransfer 3600
|
||||
TimeoutStalled 210
|
||||
TimeoutIdle 1400
|
||||
|
||||
Umask 0022 0022
|
||||
AllowOverwrite on
|
||||
|
||||
<Directory />
|
||||
<Limit ALL>
|
||||
DenyAll
|
||||
</Limit>
|
||||
</Directory>
|
||||
</Global>
|
||||
@@ -0,0 +1,93 @@
|
||||
<IfModule !mod_tls.c>
|
||||
LoadModule mod_tls.c
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_tls.c>
|
||||
<VirtualHost 0.0.0.0>
|
||||
ServerName "{{ ftp_server_name }}"
|
||||
ServerIdent on "Our head librarians Furcas and Marbas welcome you!"
|
||||
ServerAlias {{ hostvars[inventory_hostname].fqdn }} ftp.{{ hostvars[inventory_hostname].fqdn }} {{ hostvars[inventory_hostname].fqdn.split('.')[0] }}
|
||||
ServerLog /var/log/proftpd/{{ hostvars[inventory_hostname].fqdn }}.log
|
||||
Protocols ftps
|
||||
Port 990
|
||||
DefaultRoot ~
|
||||
# AllowStoreRestart on
|
||||
MaxStoreFileSize 10 Gb
|
||||
MaxTransfersPerUser STOR,RETR 9
|
||||
MaxTransfersPerHost STOR,RETR 36
|
||||
DirFakeUser on ~
|
||||
DirFakeGroup on ~
|
||||
|
||||
# AuthOrder mod_auth_pam.c mod_auth_unix.c*
|
||||
AuthOrder mod_auth_file.c
|
||||
AuthUserFile {{ config.proftpd.auth_paths.users }}
|
||||
AuthGroupFile {{ config.proftpd.auth_paths.groups }}
|
||||
AuthFileOptions SyntaxCheck
|
||||
|
||||
TLSEngine on
|
||||
TLSLog /var/log/proftpd/tls.log
|
||||
# @NOTE: "SSLv23" means all SSL versions
|
||||
TLSProtocol SSLv23
|
||||
TLSOptions AllowClientRenegotiations
|
||||
TLSVerifyClient off
|
||||
TLSRequired on
|
||||
TLSRenegotiate required off
|
||||
|
||||
TLSECCertificateFile {{ config.proftpd.tls_paths.cert }}
|
||||
TLSECCertificateKeyFile {{ config.proftpd.tls_paths.privkey }}
|
||||
TLSCACertificateFile {{ config.proftpd.tls_paths.cert }}
|
||||
|
||||
<Limit LOGIN>
|
||||
AllowUser OR {{ allowed_users}}
|
||||
</Limit>
|
||||
|
||||
<Directory ~>
|
||||
<Limit READ DIRS>
|
||||
AllowAll
|
||||
</Limit>
|
||||
</Directory>
|
||||
|
||||
<Directory ~/*>
|
||||
UserOwner ftp
|
||||
GroupOwner ftp
|
||||
HideUser !~
|
||||
HideFiles ^\.(.+)?
|
||||
HideNoAccess on
|
||||
|
||||
<Limit ALL>
|
||||
AllowAll
|
||||
</Limit>
|
||||
</Directory>
|
||||
|
||||
<Anonymous {{ anon_root }}>
|
||||
User ftp
|
||||
Group ftp
|
||||
RequireValidShell off
|
||||
DirFakeUser on anon
|
||||
DirFakeGroup on anon
|
||||
DirFakeMode 0444
|
||||
UserAlias anon {{ anon_user }}
|
||||
AllowStoreRestart off
|
||||
MaxStoreFileSize 4 Gb
|
||||
MaxTransfersPerUser STOR,RETR 3
|
||||
MaxTransfersPerHost STOR,RETR 10
|
||||
HideUser !~
|
||||
HideNoAccess on
|
||||
|
||||
<Directory {{ anon_root }}>
|
||||
<Limit READ DIRS>
|
||||
AllowAll
|
||||
</Limit>
|
||||
</Directory>
|
||||
|
||||
<Directory {{ anon_root }}/*>
|
||||
# <Limit READ DIRS MKD RMD XMKD XRMD>
|
||||
<Limit READ DIRS>
|
||||
AllowAll
|
||||
</Limit>
|
||||
|
||||
HideFiles ^\.(.+)?
|
||||
</Directory>
|
||||
</Anonymous>
|
||||
</VirtualHost>
|
||||
</IfModule>
|
||||
185
roles/bootstrap/templates/proftpd/proftpd.conf.j2
Normal file
185
roles/bootstrap/templates/proftpd/proftpd.conf.j2
Normal file
@@ -0,0 +1,185 @@
|
||||
#
|
||||
# /etc/proftpd/proftpd.conf -- This is a basic ProFTPD configuration file.
|
||||
# To really apply changes, reload proftpd after modifications, if
|
||||
# it runs in daemon mode. It is not required in inetd/xinetd mode.
|
||||
#
|
||||
|
||||
# Includes DSO modules
|
||||
Include /etc/proftpd/modules.conf
|
||||
|
||||
# Set off to disable IPv6 support which is annoying on IPv4 only boxes.
|
||||
UseIPv6 on
|
||||
# If set on you can experience a longer connection delay in many cases.
|
||||
<IfModule mod_ident.c>
|
||||
IdentLookups on
|
||||
</IfModule>
|
||||
|
||||
ServerName "{{ ftp_server_name }}"
|
||||
# Set to inetd only if you would run proftpd by inetd/xinetd/socket.
|
||||
# Read README.Debian for more information on proper configuration.
|
||||
ServerType standalone
|
||||
DeferWelcome off
|
||||
MaxInstances {{ max_conns }}
|
||||
|
||||
# Disable MultilineRFC2228 per https://github.com/proftpd/proftpd/issues/1085
|
||||
# MultilineRFC2228on
|
||||
DefaultServer on
|
||||
DefaultRoot ~
|
||||
|
||||
DenyFilter \*.*/
|
||||
|
||||
# Users require a valid shell listed in /etc/shells to login.
|
||||
# Use this directive to release that constrain.
|
||||
# RequireValidShell off
|
||||
|
||||
# Port 21 is the standard FTP port.
|
||||
Port 21
|
||||
|
||||
# If your host was NATted, this option is useful in order to
|
||||
# allow passive tranfers to work. You have to use your public
|
||||
# address and opening the passive ports used on your firewall as well.
|
||||
# MasqueradeAddress 1.2.3.4
|
||||
|
||||
# This is useful for masquerading address with dynamic IPs:
|
||||
# refresh any configured MasqueradeAddress directives every 8 hours
|
||||
# <IfModule mod_dynmasq.c>
|
||||
# DynMasqRefresh 28800
|
||||
# </IfModule>
|
||||
|
||||
# Set the user and group that the server normally runs at.
|
||||
User proftpd
|
||||
Group nogroup
|
||||
|
||||
# Uncomment this if you are using NIS or LDAP via NSS to retrieve passwords:
|
||||
# PersistentPasswd off
|
||||
|
||||
# This is required to use both PAM-based authentication and local passwords
|
||||
# AuthOrder mod_auth_pam.c* mod_auth_unix.c
|
||||
|
||||
# Be warned: use of this directive impacts CPU average load!
|
||||
# Uncomment this if you like to see progress and transfer rate with ftpwho
|
||||
# in downloads. That is not needed for uploads rates.
|
||||
#
|
||||
# UseSendFile off
|
||||
|
||||
TransferLog /var/log/proftpd/transfer.log
|
||||
SystemLog /var/log/proftpd/connection.log
|
||||
|
||||
# Logging onto /var/log/lastlog is enabled but set to off by default
|
||||
#UseLastlog on
|
||||
|
||||
# In order to keep log file dates consistent after chroot, use timezone info
|
||||
# from /etc/localtime. If this is not set, and proftpd is configured to
|
||||
# chroot (e.g. DefaultRoot or <Anonymous>), it will use the non-daylight
|
||||
# savings timezone regardless of whether DST is in effect.
|
||||
#SetEnv TZ :/etc/localtime
|
||||
|
||||
<IfModule mod_quotatab.c>
|
||||
QuotaEngine off
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_ratio.c>
|
||||
Ratios off
|
||||
</IfModule>
|
||||
|
||||
|
||||
# Delay engine reduces impact of the so-called Timing Attack described in
|
||||
# http://www.securityfocus.com/bid/11430/discuss
|
||||
# It is on by default.
|
||||
<IfModule mod_delay.c>
|
||||
DelayEngine on
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_ctrls.c>
|
||||
ControlsEngine off
|
||||
ControlsMaxClients 2
|
||||
ControlsLog /var/log/proftpd/controls.log
|
||||
ControlsInterval 5
|
||||
ControlsSocket /var/run/proftpd/proftpd.sock
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_ctrls_admin.c>
|
||||
AdminControlsEngine off
|
||||
</IfModule>
|
||||
|
||||
#
|
||||
# Alternative authentication frameworks
|
||||
#
|
||||
#Include /etc/proftpd/ldap.conf
|
||||
#Include /etc/proftpd/sql.conf
|
||||
|
||||
#
|
||||
# This is used for FTPS connections
|
||||
#
|
||||
#Include /etc/proftpd/tls.conf
|
||||
|
||||
#
|
||||
# This is used for SFTP connections
|
||||
#
|
||||
#Include /etc/proftpd/sftp.conf
|
||||
|
||||
#
|
||||
# This is used for other add-on modules
|
||||
#
|
||||
#Include /etc/proftpd/dnsbl.conf
|
||||
#Include /etc/proftpd/geoip.conf
|
||||
#Include /etc/proftpd/snmp.conf
|
||||
|
||||
#
|
||||
# Useful to keep VirtualHost/VirtualRoot directives separated
|
||||
#
|
||||
#Include /etc/proftpd/virtuals.conf
|
||||
|
||||
# A basic anonymous configuration, no upload directories.
|
||||
|
||||
# <Anonymous ~ftp>
|
||||
# User ftp
|
||||
# Group nogroup
|
||||
# # We want clients to be able to login with "anonymous" as well as "ftp"
|
||||
# UserAlias anonymous ftp
|
||||
# # Cosmetic changes, all files belongs to ftp user
|
||||
# DirFakeUser on ftp
|
||||
# DirFakeGroup on ftp
|
||||
#
|
||||
# RequireValidShell off
|
||||
#
|
||||
# # Limit the maximum number of anonymous logins
|
||||
# MaxClients 10
|
||||
#
|
||||
# # We want 'welcome.msg' displayed at login, and '.message' displayed
|
||||
# # in each newly chdired directory.
|
||||
# DisplayLogin welcome.msg
|
||||
# DisplayChdir .message
|
||||
#
|
||||
# # Limit WRITE everywhere in the anonymous chroot
|
||||
# <Directory *>
|
||||
# <Limit WRITE>
|
||||
# DenyAll
|
||||
# </Limit>
|
||||
# </Directory>
|
||||
#
|
||||
# # Uncomment this if you're brave.
|
||||
# # <Directory incoming>
|
||||
# # # Umask 022 is a good standard umask to prevent new files and dirs
|
||||
# # # (second parm) from being group and world writable.
|
||||
# # Umask 022 022
|
||||
# # <Limit READ WRITE>
|
||||
# # DenyAll
|
||||
# # </Limit>
|
||||
# # <Limit STOR>
|
||||
# # AllowAll
|
||||
# # </Limit>
|
||||
# # </Directory>
|
||||
#
|
||||
# </Anonymous>
|
||||
|
||||
<Limit LOGIN>
|
||||
DenyAll
|
||||
</Limit>
|
||||
|
||||
# Include other custom configuration files
|
||||
# !! Please note, that this statement will read /all/ file from this subdir,
|
||||
# i.e. backup files created by your editor, too !!!
|
||||
# Eventually create file patterns like this: /etc/proftpd/conf.d/*.conf
|
||||
#
|
||||
Include /etc/proftpd/conf.d/
|
||||
41
roles/bootstrap/templates/rsyncd.conf.j2
Normal file
41
roles/bootstrap/templates/rsyncd.conf.j2
Normal file
@@ -0,0 +1,41 @@
|
||||
port = 873
|
||||
use chroot = true
|
||||
max connections = 17
|
||||
ignore nonreadable = true
|
||||
pid file = /var/run/rsyncd.pid
|
||||
|
||||
[{{ ansible_facts['user_id'] }}-dl]
|
||||
path = {{ ansible_facts['user_dir'] }}/downloads/public
|
||||
uid = rika
|
||||
gid = rika
|
||||
timeout = 90
|
||||
comment = Personal download inventory
|
||||
read only = true
|
||||
exclude = .nextcloudsync.log .calnotes .caltrash .stfolder .stignore .directory .ssh *.pub
|
||||
|
||||
[{{ ansible_facts['user_id'] }}-public]
|
||||
path = {{ ansible_facts['user_dir'] }}/public/rsync
|
||||
uid = rika
|
||||
gid = rika
|
||||
timeout = 90
|
||||
comment = Public share point
|
||||
read only = true
|
||||
exclude = .nextcloudsync.log .calnotes .caltrash .stfolder .stignore .directory .ssh *.pub
|
||||
|
||||
[{{ ansible_facts['user_id'] }}-soulseek]
|
||||
path = {{ ansible_facts['user_dir'] }}/public/soulseek
|
||||
uid = rika
|
||||
gid = rika
|
||||
timeout = 90
|
||||
comment = Personal SoulSeek inventory
|
||||
read only = true
|
||||
exclude = .nextcloudsync.log .calnotes .caltrash .stfolder .stignore .directory .ssh *.pub
|
||||
|
||||
[{{ ansible_facts['user_id'] }}-portfolio]
|
||||
path = {{ ansible_facts['user_dir'] }}/portfolio
|
||||
uid = rika
|
||||
gid = rika
|
||||
timeout = 90
|
||||
comment = Personal portfolio
|
||||
read only = true
|
||||
exclude = .nextcloudsync.log .calnotes .caltrash .stfolder .stignore .directory .ssh *.pub
|
||||
@@ -1,15 +1,15 @@
|
||||
{% if list_type == 'whitelist' %}
|
||||
{% if policed_groups is not None and len(policed_groups) > 0 %}
|
||||
AllowGroups {{ policed_groups.join(' ') }}
|
||||
AllowGroups {{ ' '.join(policed_groups) }}
|
||||
{% endif %}
|
||||
{% if policed_users is not None and len(policed_users) > 0 %}
|
||||
AllowUsers {{ policed_users.join(' ') }}
|
||||
AllowUsers {{ ' '.join(policed_users) }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if policed_groups is not None and len(policed_groups) > 0 %}
|
||||
DenyGroups {{ policed_groups.join(' ') }}
|
||||
DenyGroups {{ ' '.join(policed_groups) }}
|
||||
{% endif %}
|
||||
{% if policed_users is not None and len(policed_users) > 0 %}
|
||||
DenyGroups {{ policed_users.join(' ') }}
|
||||
DenyGroups {{ ' '.join(policed_users) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -24,4 +24,5 @@ UsePAM no # enable if implementing TOTP 2FA
|
||||
PubkeyAuthentication yes
|
||||
{% else %}
|
||||
PubkeyAuthentication no
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
PrintMotd yes
|
||||
13
roles/bootstrap/templates/systemd/user/aria2cd.service.j2
Normal file
13
roles/bootstrap/templates/systemd/user/aria2cd.service.j2
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=aria2 Daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
ExecStart=/usr/bin/aria2c --conf-path={{ ansible_facts['user_dir'] }}/.config/aria2/aria2.conf
|
||||
ExecReload=/usr/bin/kill -HUP $MAINPID
|
||||
RestartSec=1min
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -5,6 +5,9 @@
|
||||
software:
|
||||
pkgs:
|
||||
# @NOTE keep fields or keys constant; otherwise will have to edit handler notifiers and listeners elsewhere
|
||||
failtwoban:
|
||||
name:
|
||||
apt: fail2ban
|
||||
gocryptfs:
|
||||
name:
|
||||
apt: gocryptfs
|
||||
@@ -80,18 +83,19 @@ software:
|
||||
crystal-docs:
|
||||
name:
|
||||
apt: crystal-doc
|
||||
javascript-lang:
|
||||
name:
|
||||
apt: nodejs
|
||||
# @TODO replace below commented with an NVM-style installation (v22): https://nodejs.org/en/download
|
||||
# javascript-lang:
|
||||
# name:
|
||||
# apt: nodejs
|
||||
# javascript-pkg:
|
||||
# name:
|
||||
# apt: npm
|
||||
# javascript-linter:
|
||||
# name:
|
||||
# apt: eslint
|
||||
javascript-docs:
|
||||
name:
|
||||
apt: nodejs-doc
|
||||
javascript-pkg:
|
||||
name:
|
||||
apt: npm
|
||||
javascript-linter:
|
||||
name:
|
||||
apt: eslint
|
||||
php-lang:
|
||||
name:
|
||||
apt: php
|
||||
@@ -104,6 +108,9 @@ software:
|
||||
php-pkg:
|
||||
name:
|
||||
apt: composer
|
||||
# php-ldap:
|
||||
# name:
|
||||
# apt: php-ldap
|
||||
html-linter:
|
||||
name:
|
||||
apt: tidy
|
||||
@@ -119,6 +126,9 @@ software:
|
||||
distrobox:
|
||||
name:
|
||||
apt: distrobox
|
||||
fastfetch:
|
||||
name:
|
||||
apt: fastfetch
|
||||
# @TODO manually install the commented below on current active new VPS, then uncomment
|
||||
# duplicity:
|
||||
# name:
|
||||
@@ -180,18 +190,32 @@ software:
|
||||
# slapd:
|
||||
# name:
|
||||
# apt: slapd
|
||||
# proftpd:
|
||||
# name:
|
||||
# apt: proftpd
|
||||
# rsync:
|
||||
# name:
|
||||
# apt: rsync
|
||||
proftpd-mod-crypto:
|
||||
name:
|
||||
apt: proftpd-mod-crypto
|
||||
# @TODO write configuration files and handler for below two package installations
|
||||
# based on:
|
||||
clamav:
|
||||
name:
|
||||
apt: clamav
|
||||
clamd:
|
||||
name:
|
||||
apt: clamav-daemon
|
||||
proftpd:
|
||||
name:
|
||||
apt: proftpd
|
||||
proftpd-docs:
|
||||
name:
|
||||
apt: proftpd-doc
|
||||
rsync:
|
||||
name:
|
||||
apt: rsync
|
||||
# rclone:
|
||||
# name:
|
||||
# apt: rsync
|
||||
# aria:
|
||||
# name:
|
||||
# apt: aria2
|
||||
# apt: rclone
|
||||
aria:
|
||||
name:
|
||||
apt: aria2
|
||||
# mopidy:
|
||||
# name:
|
||||
# apt: mopidy
|
||||
@@ -201,8 +225,73 @@ software:
|
||||
# caddy:
|
||||
# name:
|
||||
# apt: caddy
|
||||
snaps:
|
||||
nextcloud:
|
||||
name: nextcloud
|
||||
channel: ~
|
||||
opts:
|
||||
- "nextcloud:php.memory-limit=512M"
|
||||
- "nextcloud:nextcloud.cron-interval=10m"
|
||||
- "nextcloud:http.compression=true"
|
||||
- "nextcloud:ports.http=81"
|
||||
# @TODO see how to set these options: https://help.nextcloud.com/t/how-to-configure-nextcloud-snap/216036#p-649442-trusted-domains-configuration-8
|
||||
# @TODO see how to set these options: https://help.nextcloud.com/t/how-to-configure-nextcloud-snap/216036#p-649442-trusted-proxy-configuration-9
|
||||
links:
|
||||
quartz:
|
||||
name: quartz
|
||||
src: https://github.com/jackyzha0/quartz.git
|
||||
branch: v4
|
||||
version: ~
|
||||
output: ~
|
||||
config:
|
||||
git:
|
||||
sys:
|
||||
editor: nvim
|
||||
proftpd:
|
||||
name: "{{ hostvars[inventory_hostname].fqdn.split('.')[0] }}"
|
||||
auth_paths:
|
||||
users: /etc/proftpd/ftpd.passwd
|
||||
groups: /etc/proftpd/ftpd.group
|
||||
msg:
|
||||
welcome: "Our head librarians Furcas and Marbas welcome you!"
|
||||
users:
|
||||
webmaster:
|
||||
username: webmaster
|
||||
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
|
||||
63633938633139636663623166343836643839306538373762393834393230336334383334303163
|
||||
3465323831366163386265353664313932383664373838660a363463303364373963353638396462
|
||||
65356135623030653533333766623865643065303739386538636662303537376466333039613363
|
||||
3932313334643163650a303336623031613964356433363536373236303266663735343939383930
|
||||
3636
|
||||
services: [http,https]
|
||||
smuggler:
|
||||
username: smuggler
|
||||
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
|
||||
38396565313866383761303137343431613830643436666431316434393362623035623031656263
|
||||
6537313630393433336133643166363564383163616232320a623034636664353864613862353366
|
||||
38303663363665663366336131663431383936306131616262376162653837326163393561323465
|
||||
3734333031323330300a353562353035323731303732323534613938353935393433646235356137
|
||||
62336333666362383665623466353337303134623966663061366235303261653333
|
||||
services: []
|
||||
tls_paths:
|
||||
cert: "/usr/local/share/ca-certificates/{{ hostvars[inventory_hostname].fqdn }}.crt"
|
||||
privkey: "/usr/local/share/ca-certificates/{{ hostvars[inventory_hostname].fqdn }}.key"
|
||||
nextcloud:
|
||||
users:
|
||||
admin:
|
||||
username: admin
|
||||
# @TODO change this password to ansible-vaulted actual choice password later
|
||||
password: password123 # @NOTE placeholder
|
||||
phone_region: US
|
||||
aria:
|
||||
checksum: ~
|
||||
api_key: ~
|
||||
|
||||
|
||||
12
tasks.org
12
tasks.org
@@ -3,12 +3,12 @@
|
||||
#+language: en
|
||||
|
||||
* PLANNED
|
||||
** TODO [#A] Add more sections to ~README.md~, as enlisted below
|
||||
- Section about inventory conventional groups, with subsection on essential or avaialble host/group variables
|
||||
- Section about available roles, with subsection on essential or available role variables
|
||||
- Section about available playbooks, with subsection on essential or available playbook variables
|
||||
- Section about conventional external variable files, their location(s) and the semantics of their filenames
|
||||
- Section about conventions for handling sensitive information, and for directory tree structures under ~${SUKAATO_ANSIBLE_PROJECT}/playbooks/{files,templates}~ or ~${SUKAATO_ANSIBLE_PROJECT}/.ansible/roles/**/{files,templates}~
|
||||
** 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user