Compare commits

..

44 Commits

Author SHA1 Message Date
5d99bf51da . 2026-05-30 06:36:10 -04:00
5ff9ca4687 removed user primary group to enable default action, implemented solution for data restructure to avoid nested looping necessity, re-ordered last SSH access tasks 2026-05-30 06:35:39 -04:00
d364f82c9f added conjunctive test case for root to task conditional and fixed copy module's valdation string argument 2026-05-30 06:33:25 -04:00
ce62e4afa6 . 2026-05-30 06:09:31 -04:00
f2be3f4899 inventory host test file removed due to including network information 2026-05-30 06:09:05 -04:00
fd5f6f5bca . 2026-05-30 06:08:28 -04:00
eb7bb02e86 excluded official playbook from version control 2026-05-30 06:06:47 -04:00
7f3bb699f9 added example playbook file instead 2026-05-30 06:05:57 -04:00
7d73885162 removed official playbook file 2026-05-30 06:05:30 -04:00
3be40169b2 added a 'local_facts' playbook varable to be used by some init-server role tasks during localhost delegation 2026-05-30 06:02:44 -04:00
08053e6c39 added an example inventory host file, showing expected structure 2026-05-30 06:01:41 -04:00
5073008506 ignoring test inventory host file 2026-05-30 06:00:49 -04:00
0ab26cae67 changed dictionary attribute or variable name 'keys' to 'ssh_keys' due to possible reservation of prior 2026-05-30 05:51:48 -04:00
4920837641 Set host_key_checking to False 2026-05-30 05:50:19 -04:00
8764bede85 template no longer needed as task previously using it now uses blockinfile module 2026-05-29 08:29:12 -04:00
c751ced793 writing script to simplify, or abstarct from, use of ansible commands for convenience 2026-05-29 08:28:21 -04:00
03a1a5879e added a playbook, mostly still used primarily for testing purposes 2026-05-29 08:27:09 -04:00
1ecff67cd9 separated out an SSH hardening task as part of refactor 2026-05-29 08:21:15 -04:00
8e9b993f14 created task inclusion handlers to allow for sequential multi-tasks or task blocks that can be called by package installation tasks elsewhere 2026-05-29 08:20:31 -04:00
659feb3322 changed variable in conditional case for task to 'ansible_user' and uncommented user module task groups list item 2026-05-29 08:13:40 -04:00
00486fbc8d changed variable used in conditional case, altered task modifying/creating sftp configuration file for SSH to use blockinfile module 2026-05-29 07:40:38 -04:00
f7ba34ec69 due to refactor, shortened and renamed file to only take care of spawning the server and adding SSH public keys to root user account 2026-05-29 07:32:28 -04:00
5440fd3acb further specified task names, corrected task conditional case tests 2026-05-29 07:24:10 -04:00
c1af7193f7 re-encrypted admin user password for armitage host 2026-05-29 07:18:31 -04:00
f39bb9c8a3 commented out stdout_callback and set callback_result_format to 'yaml' 2026-05-29 06:55:31 -04:00
40fa1312c4 excluding backup files from version control 2026-05-29 06:49:37 -04:00
8ba6a236f8 excluded Ansible runtime cache from version control 2026-05-27 15:02:39 -04:00
4f9ecc84d3 added a playbook to continue working on 2026-05-27 14:32:59 -04:00
217ace503f added YAML document division 2026-05-27 14:32:45 -04:00
a614f4461f enabling task debugger in Ansible configuration 2026-05-27 14:08:41 -04:00
4a658857b3 added an example/test YAML inventory file 2026-05-27 14:01:06 -04:00
9f85033aff excluded official host files for privacy purposes, but added a test or example host file 2026-05-27 14:00:31 -04:00
9aac725e88 added inventory sources to ansible configuration 2026-05-27 13:47:09 -04:00
c45946739e added check for whether administrative or root login used 2026-05-27 13:14:14 -04:00
4c9a4d480e added task checking for administrative login use 2026-05-27 13:12:57 -04:00
020fcf2c51 added requirement to some tasks for system case to be linux kernel, and added tag 2026-05-27 13:12:08 -04:00
6a2179d7a0 added tagged task that adds SSH authentication restrictions 2026-05-27 13:10:45 -04:00
6eaeeb0322 modularized component for creating administrative users with SSH access 2026-05-27 13:09:19 -04:00
f01f1b5431 automated changes 2026-05-27 12:08:17 -04:00
9c770faa23 added Ansible linter configuration file 2026-05-27 12:05:19 -04:00
7dae1fc086 added more python packages and mdns-related avahi packages 2026-05-27 11:58:22 -04:00
43cfc07c11 added more python packages 2026-05-27 11:57:03 -04:00
3a558c5bad added ansible-galaxy package as python requirement 2026-05-27 11:12:47 -04:00
b6b4aad798 excluding lock files from version control 2026-05-27 11:11:31 -04:00
24 changed files with 653 additions and 547 deletions

129
.ansible-lint.yml Normal file
View File

@@ -0,0 +1,129 @@
---
# .ansible-lint
profile: null # min, basic, moderate,safety, shared, production
# Allows dumping of results in SARIF format
# sarif_file: result.sarif
# exclude_paths included in this file are parsed relative to this file's location
# and not relative to the CWD of execution. CLI arguments passed to the --exclude
# option are parsed relative to the CWD of execution.
exclude_paths:
- .cache/ # implicit unless exclude_paths is defined in config
- test/fixtures/formatting-before/
- test/fixtures/formatting-prettier/
# quiet: true
# strict: true
# verbosity: 1
# Mock modules or roles in order to pass ansible-playbook --syntax-check
mock_modules:
- zuul_return
# note the foo.bar is invalid as being neither a module or a collection
- fake_namespace.fake_collection.fake_module
- fake_namespace.fake_collection.fake_module.fake_submodule
mock_roles:
- mocked_role
- author.role_name # old standalone galaxy role
- fake_namespace.fake_collection.fake_role # role within a collection
# Enable checking of loop variable prefixes in roles
loop_var_prefix: "^(__|{role}_)"
# Enforce variable names to follow pattern below, in addition to Ansible own
# requirements, like avoiding python identifiers. To disable add `var-naming`
# to skip_list.
var_naming_pattern: "^[a-z_][a-z0-9_]*$"
use_default_rules: true
# Load custom rules from this specific folder
# rulesdir:
# - ./rule/directory/
# Ansible-lint is able to recognize and load skip rules stored inside
# `.ansible-lint-ignore` (or `.config/ansible-lint-ignore.txt`) files.
# To skip a rule just enter filename and tag, like "playbook.yml package-latest"
# on a new line.
# Optionally you can add comments after the tag, prefixed by "#". We discourage
# the use of skip_list below because that will hide violations from the output.
# When putting ignores inside the ignore file, they are marked as ignored, but
# still visible, making it easier to address later.
skip_list:
- skip_this_tag
# Ansible-lint does not automatically load rules that have the 'opt-in' tag.
# You must enable opt-in rules by listing each rule 'id' below.
enable_list:
- args
- empty-string-compare # opt-in
- no-log-password # opt-in
- no-same-owner # opt-in
- name[prefix] # opt-in
- galaxy-version-incorrect # opt-in
# add yaml here if you want to avoid ignoring yaml checks when yamllint
# library is missing. Normally its absence just skips using that rule.
- yaml
# Report only a subset of tags and fully ignore any others
# tags:
# - jinja[spacing]
# Ansible-lint does not fail on warnings from the rules or tags listed below
warn_list:
- skip_this_tag
- experimental # experimental is included in the implicit list
# - role-name
# - yaml[document-start] # you can also use sub-rule matches
# Some rules can transform files to fix (or make it easier to fix) identified
# errors. `ansible-lint --fix` will reformat YAML files and run these transforms.
# By default it will run all transforms (effectively `write_list: ["all"]`).
# You can disable running transforms by setting `write_list: ["none"]`.
# Or only enable a subset of rule transforms by listing rules/tags here.
# write_list:
# - all
# Offline mode disables installation of requirements.yml and schema refreshing
offline: true
# Define required Ansible's variables to satisfy syntax check
extra_vars:
foo: bar
multiline_string_variable: |
line1
line2
complex_variable: ":{;\t$()"
# Uncomment to enforce action validation with tasks, usually is not
# needed as Ansible syntax check also covers it.
# skip_action_validation: false
# List of additional kind:pattern to be added at the top of the default
# match list, first match determines the file kind.
kinds:
# - playbook: "**/examples/*.{yml,yaml}"
# - galaxy: "**/folder/galaxy.yml"
# - tasks: "**/tasks/*.yml"
# - vars: "**/vars/*.yml"
# - meta: "**/meta/main.yml"
- yaml: "**/*.yaml-too"
# List of additional collections to allow in only-builtins rule.
# only_builtins_allow_collections:
# - example_ns.example_collection
# List of additions modules to allow in only-builtins rule.
# only_builtins_allow_modules:
# - example_module
# Allow setting custom prefix for name[prefix] rule
task_name_prefix: "{stem} | "
# Complexity related settings
# Limit the depth of the nested blocks:
# max_block_depth: 20
# Also recognize these versions of Ansible as supported:
# supported_ansible_also:
# - "2.18"

View File

@@ -0,0 +1,32 @@
# This is a mocked Ansible module generated by ansible-lint
from ansible.module_utils.basic import AnsibleModule
DOCUMENTATION = '''
module: fake_namespace.fake_collection.fake_module
short_description: Mocked
version_added: "1.0.0"
description: Mocked
author:
- ansible-lint (@nobody)
'''
EXAMPLES = '''mocked'''
RETURN = '''mocked'''
def main():
result = dict(
changed=False,
original_message='',
message='')
module = AnsibleModule(
argument_spec=dict(),
supports_check_mode=True,
)
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,32 @@
# This is a mocked Ansible module generated by ansible-lint
from ansible.module_utils.basic import AnsibleModule
DOCUMENTATION = '''
module: fake_namespace.fake_collection.fake_module.fake_submodule
short_description: Mocked
version_added: "1.0.0"
description: Mocked
author:
- ansible-lint (@nobody)
'''
EXAMPLES = '''mocked'''
RETURN = '''mocked'''
def main():
result = dict(
changed=False,
original_message='',
message='')
module = AnsibleModule(
argument_spec=dict(),
supports_check_mode=True,
)
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,32 @@
# This is a mocked Ansible module generated by ansible-lint
from ansible.module_utils.basic import AnsibleModule
DOCUMENTATION = '''
module: zuul_return
short_description: Mocked
version_added: "1.0.0"
description: Mocked
author:
- ansible-lint (@nobody)
'''
EXAMPLES = '''mocked'''
RETURN = '''mocked'''
def main():
result = dict(
changed=False,
original_message='',
message='')
module = AnsibleModule(
argument_spec=dict(),
supports_check_mode=True,
)
module.exit_json(**result)
if __name__ == "__main__":
main()

9
.gitignore vendored
View File

@@ -1,4 +1,11 @@
# Ansible Tower ignore list
/hosts
/hosts.test.yml
/hosts.yml
/hosts.yaml
/hosts.json
*.bak
/init@homeserver.yml
# Ansible runtime and backups
*.original
@@ -11,6 +18,8 @@
.galcache/
/collections/ansible_collections/
/.devcontainer/
.lock
/.cache/
# Try tyo avoid any plain-text passwords
*pwd*

View File

@@ -96,6 +96,7 @@ action_plugins=plugins/action:~/.ansible/plugins/action:/usr/share/ansible/plugi
# (pathspec) Colon-separated paths in which Ansible will search for Cache Plugins.
cache_plugins=plugins/cache:~/.ansible/plugins/cache:/usr/share/ansible/plugins/cache
callback_result_format=yaml
# (pathspec) Colon-separated paths in which Ansible will search for Callback Plugins.
callback_plugins=plugins/callback:~/.ansible/plugins/callback:/usr/share/ansible/plugins/callback
@@ -138,7 +139,7 @@ gathering=smart
;hash_behaviour=replace
# (pathlist) Comma-separated list of Ansible inventory sources
inventory=['.','inv','/etc/ansible/hosts']
inventory=['hosts','hosts.yml','hosts.yaml','hosts.json','/etc/ansible/hosts','/etc/ansible/hosts.yml','/etc/ansible/hosts.yaml','/etc/ansible/hosts.json']
# (pathspec) Colon-separated paths in which Ansible will search for HttpApi Plugins.
httpapi_plugins=plugins/httpapi:~/.ansible/plugins/httpapi:/usr/share/ansible/plugins/httpapi
@@ -233,7 +234,7 @@ 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.
# See :ref:`callback_plugins` for a list of available options.
stdout_callback=yaml
;stdout_callback=yaml
# (string) Set the default strategy used for plays.
;strategy=linear
@@ -318,7 +319,7 @@ doc_fragment_plugins=plugins/doc_fragments:~/.ansible/plugins/doc_fragments:/usr
# (boolean) Whether or not to enable the task debugger, this previously was done as a strategy plugin.
# Now all strategy plugins can inherit this behavior. The debugger defaults to activating when
# a task is failed on unreachable. Use the debugger keyword for more flexibility.
;enable_task_debugger=False
enable_task_debugger=True
# (boolean) Toggle to allow missing handlers to become a warning instead of an error when notifying.
;error_on_missing_handler=True
@@ -331,6 +332,7 @@ doc_fragment_plugins=plugins/doc_fragments:~/.ansible/plugins/doc_fragments:/usr
# (boolean) Set this to "False" if you want to avoid host key checking by the underlying connection plugin Ansible uses to connect to the host.
# Please read the documentation of the specific connection plugin used for details.
;host_key_checking=True
host_key_checking=False
# (boolean) Facts are available inside the `ansible_facts` variable, this setting also pushes them as their own vars in the main namespace.
# Unlike inside the `ansible_facts` dictionary where the prefix `ansible_` is removed from fact names, these will have the exact names that are returned by the module.
@@ -663,7 +665,7 @@ token_path=.gal_token
;any_unparsed_is_failed=False
# (list) List of enabled inventory plugins, it also determines the order in which they are used.
;enable_plugins=host_list, script, auto, yaml, ini, toml
enable_plugins=host_list, script, auto, yaml, ini, toml
# (bool) Controls if ansible-inventory will accurately reflect Ansible's view into inventory or its optimized for exporting.
;export=False

3
dry_run.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
ansible-playbook --ask-pass --ask-become-pass -i hosts.yml init@homeserver.yml --check

View File

@@ -13,26 +13,26 @@ instance: armitage
# operating_system: "tftp://hikiki.local:69/debian.iso"
operating_system: ~
# <list[<str>]> of control node or local SSH key basenames
keys:
ssh_keys:
- id_ed25519_localhost
# <list<dict>> list of administrative users (in Linux, users that can use "sudo")
admins:
- username: admin # <str> arbitrary valid user name
services: ~ # <list[<str>]> if linux system user, assocated servce
# <list[<str>]> list of control node or local SSH key basenames for this user
keys: "{{ keys }}"
ssh_keys: "{{ keys }}"
# <str<vault?>> hashed (and maybe salted) password
password: !vault |
$ANSIBLE_VAULT;1.1;AES256
34396235306630656138303939346638343135623430353666326462663131613130643061366435
6563616331656566626263633966633764386564383961640a656466323835616263653531323861
65376663363934653163313666303166376262623334343034626535356431636662366261333061
3866656638623631660a386666383136396238633365333465333766383766303631663336326264
35663339663062333162643039663430363265393163303839356664343633373630303462393735
37316262383335323837646265336139373238623735383134623361363136663436393162666336
62353462323534316531313533636461353139326466646662356233373130616633633262616539
37306332666338363231383537343832396432666134663462633336646330646332306634356636
36626166386634653537613334616538313266323866303738316430666131646333
33663131343861303735643439393165356231366338346538333537643464343761373139303364
6630303563346437373161626662313432306138353132350a353334356139376662333562353834
36326461613664616565373835303636636533616462303732633461343130346134366662373566
6431623034653363310a303665636366353535313436666532623737373930356364616339313633
34663839656637373031393031656332393761623161643730326563323863363461333864353338
30633964353339323465643064636538346464343035626461333366303835333039653661383030
62656663336536373262623062633563646434646431303137306438633937323764633334396539
64353734613662663063343966356562326661626436663430623430663766343030646333306634
32353839313235313339353431323837356537336231366564313431313462613333
pkgs:
# <dict[<str>:<dict>]> representing package groups installed by package manager via repositories
mngr:
@@ -207,6 +207,18 @@ pkgs:
key_path: ~
src_entry: ~
src_path: ~
- name: python3-venv
uri: ~
key: ~
key_path: ~
src_entry: ~
src_path: ~
- name: python3-pip
uri: ~
key: ~
key_path: ~
src_entry: ~
src_path: ~
- name: golang
uri: ~
key: ~
@@ -238,6 +250,18 @@ pkgs:
src_entry: ~
src_path: ~
handler: ~
- name: avahi-daemon
uri: ~
key: ~
key_path: ~
src_entry: ~
src_path: ~
- name: avahi-utils
uri: ~
key: ~
key_path: ~
src_entry: ~
src_path: ~
# <dict[<str>:<dict>]> representing package groups installed by shell scripts
script:
# <list[<dict>]> representing user-level or supplemental shell script installations
@@ -254,6 +278,10 @@ pkgs:
src: "https://install.julialang.org"
pre: ~
post: ~
- name: uv
src: "https://astral.sh/uv/install.sh"
pre: ~
post: ~
# <dict[<str>:<dict>]> representing package groups installed from source archives
archive:
# <list[<dict>]> representing user-level or supplemental source archives

View File

@@ -8,7 +8,7 @@ instance: ""
# Example-- operating_system: "tftp://hikiki.local:69/debian.iso"
operating_system: ~
# <list[<str>]> of control node or local SSH key basenames
keys: []
ssh_keys: []
# <dict[<str>:<dict>]> package groups
pkgs:
# <dict[<str>:<dict>]> representing package groups installed by package manager via repositories

View File

@@ -24,7 +24,7 @@ origin: us-east
# <str<enum>> representing Linux distro or OS image available in VPS service to be used for VPS
operating_system: linode/debian13
# <list[<str>]> list of control node or local SSH key basenames for root user
keys:
ssh_keys:
- id_ecdsa-sha2_sukaato_miniyubikey
- id_ecdsa-sha2_sukaato_yubikey
# <list<dict>> list of administrative users (in Linux, users that can use "sudo")
@@ -32,7 +32,7 @@ admins:
- username: senpai # <str> arbitrary valid user name
services: ~ # <list[<str>]> if linux system user, assocated servce
# <list[<str>]> list of control node or local SSH key basenames for this user
keys:
ssh_keys:
- id_ed25519_sukaato_yubikey
- id_ed25519_sukaato_miniyubikey
# <str<vault?>> hashed (and maybe salted) password
@@ -222,6 +222,18 @@ pkgs:
key_path: ~
src_entry: ~
src_path: ~
- name: python3-venv
uri: ~
key: ~
key_path: ~
src_entry: ~
src_path: ~
- name: python3-pip
uri: ~
key: ~
key_path: ~
src_entry: ~
src_path: ~
- name: golang
uri: ~
key: ~
@@ -269,6 +281,10 @@ pkgs:
src: "https://install.julialang.org"
pre: ~
post: ~
- name: uv
src: "https://astral.sh/uv/install.sh"
pre: ~
post: ~
# <dict[<str>:<dict>]> representing package groups installed from source archives
archive:
# <list[<dict>]> representing user-level or supplemental source archives

View File

@@ -9,7 +9,7 @@ origin: ""
# <str<enum>> representing Linux distro or OS image available in VPS service to be used for VPS
operating_system: ~
# <list[<str>]> of control node or local SSH key basenames
keys: []
ssh_keys: []
# <dict[<str>:<dict>]> package groups
pkgs:
# <dict[<str>:<dict>]> representing package groups installed by package manager via repositories

11
hosts.yml.example Normal file
View File

@@ -0,0 +1,11 @@
# @TODO use hosts and host groupings that refer or point to VM or containerized servers for testing
ungrouped:
hosts: ~
sukaato:
hosts: ~
armitage:
hosts: ~
vps:
children: ~
homeserver:
children: ~

View File

@@ -0,0 +1,28 @@
# @NOTE run 'ansible-playbook' command on this using 'sudo'
- name: Initialize homeserver
hosts: armitage
remote_user: root
vars:
harden: true
local_facts:
user_dir: ~
user_id: ~
tasks:
- name: Hardening SSH server
ansible.builtin.include_role:
name: init-server # required. The name of the role to be executed.
# apply: # not required. Accepts a hash of task keywords (e.g. C(tags), C(become)) that will be applied to all tasks within the included role.
tasks_from: harden # not required. File to load from a role's C(tasks/) directory.
# vars_from: main # not required. File to load from a role's C(vars/) directory.
# defaults_from: main # not required. File to load from a role's C(defaults/) directory.
# allow_duplicates: True # not required. Overrides the role's metadata setting to allow using a role more than once with the same parameters.
# handlers_from: main # not required. File to load from a role's C(handlers/) directory.
- name: Initializing groups and users
ansible.builtin.include_role:
name: init-server # required. The name of the role to be executed.
# apply: # not required. Accepts a hash of task keywords (e.g. C(tags), C(become)) that will be applied to all tasks within the included role.
tasks_from: ssh-users # not required. File to load from a role's C(tasks/) directory.
vars_from: main # not required. File to load from a role's C(vars/) directory.
defaults_from: main # not required. File to load from a role's C(defaults/) directory.
# allow_duplicates: True # not required. Overrides the role's metadata setting to allow using a role more than once with the same parameters.
# handlers_from: main # not required. File to load from a role's C(handlers/) directory.

View File

@@ -1,11 +1,15 @@
ansible==13.7.0
ansible-builder==3.1.1
ansible-compat==26.3.0
ansible-core==2.20.6
ansible-lint==26.4.0
ansible-navigator==26.4.0
ansible-runner==2.4.3
ansible-specdoc==0.0.20
appdirs==1.4.4
attrs==26.1.0
baron==0.10.1
bindep==2.14.0
black==26.5.1
bracex==2.6
certifi==2026.5.20
@@ -13,29 +17,36 @@ cffi==2.0.0
charset-normalizer==3.4.7
click==8.4.1
cryptography==48.0.0
Deprecated==1.3.1
deprecated==1.3.1
distro==1.9.0
enrich==1.2.7
filelock==3.29.0
idna==3.16
Jinja2==3.1.6
jinja2==3.1.6
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
linode_api4==5.44.0
linode-api4==5.44.0
lockfile==0.12.2
markdown-it-py==4.2.0
MarkupSafe==3.0.3
markupsafe==3.0.3
mdurl==0.1.2
molecule==26.4.0
mypy_extensions==1.1.0
mypy-extensions==1.1.0
onigurumacffi==1.5.0
packaging==26.2
parsley==1.3
pathspec==1.0.4
pbr==7.0.3
pexpect==4.9.0
platformdirs==4.9.6
pluggy==1.6.0
polling==0.3.2
ptyprocess==0.7.0
pycparser==3.0
Pygments==2.20.0
pygments==2.20.0
python-daemon==3.1.2
pytokens==0.4.1
PyYAML==6.0.3
pyyaml==6.0.3
redbaron==0.9.2
referencing==0.37.0
requests==2.34.2
@@ -43,9 +54,11 @@ resolvelib==1.2.1
rich==15.0.0
rpds-py==0.30.0
rply==0.7.8
ruamel.yaml==0.19.1
ruamel.yaml.clib==0.2.15
ruamel-yaml==0.19.1
ruamel-yaml-clib==0.2.15
setuptools==82.0.1
subprocess-tee==0.4.2
tzdata==2026.2
urllib3==2.7.0
wcmatch==10.1
wrapt==2.2.1

View File

@@ -1,2 +1,5 @@
#SPDX-License-Identifier: MIT-0
---
# defaults file for roles/init-vps
# <str<bool>> whether to harden managed node's SSH service
harden: true

View File

@@ -1,54 +1,7 @@
# SPDX-License-Identifier: MIT-0
---
# handlers file for roles/init-vps
- name: Executing relevant files for software installation from git repository
block:
- name: Finalizing quartz installation
listen: quartz
block:
- name: Installing NodeJS dependencies of quartz software
community.general.npm:
executable: "{{ ansible_facts['user_dir'] }}/.nvm/versions/node/v24.11.1/lib/node_modules/npm"
path: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
state: latest
- name: Configuring quartz software
block:
- name: Initializing quartz website
ansible.builtin.command:
chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
cmd: npx quartz create
register: stdout
changed_when: stdout.rc == 0
- name: Installing quartz plugins referenced in website template
ansible.builtin.command:
chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
cmd: npx quartz plugin install --from-config
register: stdout
changed_when: stdout.rc == 0
# - name: Starting quartz site web server
# ansible.builtin.command:
# chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
# cmd: npx quartz build --serve
# register: stdout
# changed_when: stdout
- name: Committing requisite actions for building software from source archives
block:
- name: Finalizing building of Surge
listen: surge
block:
- name: Hardlinking Surge executable
ansible.builtin.file:
src: "{{ ansible_facts['user_dir'] }}/downloads/archives/released/surge/surge"
dest: "{{ ansible_facts['user_dir'] }}/.local/bin/surge"
state: hard
mode: "755"
- name: Copying Surge executable
become: true
ansible.builtin.copy:
src: "{{ ansible_facts['user_dir'] }}/downloads/archives/released/surge/surge"
dest: /usr/bin/surge
owner: root
group: root
mode: "755"
force: true
backup: false
- name: Setting up Quartz
ansible.builtin.include_tasks:
file: tasks/contingent/pkg/quartz.yml
listen: quartz

View File

@@ -0,0 +1,26 @@
---
- name: Installing NodeJS dependencies of quartz software
community.general.npm:
executable: "{{ ansible_facts['user_dir'] }}/.nvm/versions/node/v24.11.1/lib/node_modules/npm"
path: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
state: latest
- name: Configuring quartz software
block:
- name: Initializing quartz website
ansible.builtin.command:
chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
cmd: npx quartz create
register: stdout
changed_when: stdout.rc == 0
- name: Installing quartz plugins referenced in website template
ansible.builtin.command:
chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
cmd: npx quartz plugin install --from-config
register: stdout
changed_when: stdout.rc == 0
# - name: Starting quartz site web server
# ansible.builtin.command:
# chdir: "{{ ansible_facts['user_dir'] }}/repos/.foreign/quartz"
# cmd: npx quartz build --serve
# register: stdout
# changed_when: stdout

View File

@@ -0,0 +1,18 @@
#SPDX-License-Identifier: MIT-0
---
# tasks file for roles/init-vps
- name: Checking whether administrative login used
when: ansible_user not in (admins | map(attribute="username") | list) and ansible_user != "root"
ansible.builtin.fail:
msg: Must use administrative user for subsequent tasks
- name: Hardening SSH service for the Linode VPS
ansible.builtin.copy:
src: sshd_config.d/harden.conf
dest: /etc/ssh/sshd_config.d/harden.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: 'sshd -t -f %s'
register: ssh_hardened

View File

@@ -1,16 +1,25 @@
#SPDX-License-Identifier: MIT-0
---
# tasks file for roles/init-vps
- name: Creating prerequisite directory tree
- name: Checking whether administrative login used
when: ansible_user not in (admins | map(attribute="username") | list)
ansible.builtin.fail:
msg: Must use administrative user for subsequent tasks
- name: Creating prerequisite directory tree for installation scripts
ansible.builtin.file:
path: "{{ ansible_facts['user_dir'] }}/.local/bin"
recurse: true
state: directory
- name: Creating prerequisite directory tree
- name: Creating prerequisite directory tree for unarchived archives
ansible.builtin.file:
path: "{{ ansible_facts['user_dir'] }}/downloads/archives/released"
recurse: true
state: directory
- name: Creating prerequisite directory tree for package installation executables
ansible.builtin.file:
path: "{{ ansible_facts['user_dir'] }}/.local_pkgs"
recurse: true
state: directory
- name: Installing Linux software
when: ansible_facts["system"] == "Linux"
block:
@@ -19,7 +28,7 @@
become: true
block:
- name: Registering a package signing key
when: item.key is defined and item.key_path is defined
when: item.key != None and item.key_path != None
ansible.builtin.get_url:
url: "{{ item.key }}"
dest: "{{ item.key_path | default('/etc/apt/keyrings/') }}"
@@ -29,14 +38,16 @@
force: true
backup: true
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace | rejectattr('key', 'search', '\\.deb$') }}"
- name: Premature stop
ansible.builtin.meta: end_play
- name: Installing a package signing key
when: item.key is defined
when: item.key != None
ansible.builtin.apt:
deb: "{{ item.key }}"
state: present
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace | selectattr('key', 'search', '\\.deb$') }}"
- name: Registering a package source
when: item.src_entry is defined and item.src_path is defined
when: item.src_entry != None and item.src_path != None
ansible.builtin.copy:
content: "{{ item.src_entry }}"
dest: "{{ item.src_path }}"
@@ -47,7 +58,7 @@
backup: true
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace }}"
- name: Installing a local package in managed node
when: item.uri is defined
when: item.uri != None
ansible.builtin.apt:
deb: "{{ item.uri }}"
update_cache: true
@@ -55,7 +66,7 @@
notify: "{{ item.name }}"
loop: "{{ pkgs.mngr.core + pkgs.mngr.userspace | selectattr('uri', 'search', '\\.deb$') }}"
- name: Installing a package
when: item.name is defined and item.uri is undefined
when: item.name != None and item.uri == None
ansible.builtin.package:
name: "{{ item.name }}"
update_cache: true
@@ -65,7 +76,7 @@
tags:
- get_mngr_pkgs
- name: Installing software by executing installation shell scripts
when: item.src is defined
when: item.src != None
block:
- name: Acquiring installation shell script
ansible.builtin.get_url:

View File

@@ -3,11 +3,26 @@
# tasks file for roles/init-vps
# @TODO complete below tasks
- name: Checking whether administrative login used
when: ansible_facts["user_id"] not in (admins | map(attribute="username") | list)
when: ansible_user not in (admins | map(attribute="username") | list)
ansible.builtin.fail:
msg: Administrative user does not exist on managed node
msg: Must use administrative user for subsequent tasks
- name: Setting approved SSH authentication procedures
when: harden and ansible_facts["system"] == "Linux"
become: true
ansible.builtin.copy:
src: sshd_config.d/auth.conf
dest: /etc/ssh/sshd_config.d/auth.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t %s"
register: ssh_authenticator
tags:
- ssh_secure_auth
- name: Prohibiting SSH root login
when: harden
when: harden and ansible_facts["system"] == "Linux"
become: true
ansible.builtin.copy:
src: sshd_config.d/denyroot.conf
@@ -19,7 +34,7 @@
backup: true
validate: "sshd -t %s"
- name: Create groups for FTP services
when: "'internal-sftp' in item.service or 'proftpd' in item.service or 'vsftpd' in item.service"
when: "'sftp-server' in item.service or 'proftpd' in item.service or 'vsftpd' in item.service"
become: true
ansible.builtin.group:
name: "{{ item.username }}"
@@ -28,14 +43,27 @@
loop: "{{ sys_users }}"
register: ftp_groups
- name: Configuring SFTP for FTP group
when: ansible_facts["system"] == "Linux"
become: true
ansible.builtin.template:
src: sshd_config.d/sftp.conf.j2
dest: /etc/ssh/sshd_config.d/sftp.conf
ansible.builtin.blockinfile:
# src: sshd_config.d/sftp.conf.j2
# dest: /etc/ssh/sshd_config.d/sftp.conf
path: /etc/ssh/sshd_config.d/sftp.conf
block: |
Match Group {{ item.name }}
ForceCommand internal-sftp -d /%u
ChrootDirectory /srv/{{ item.name}}
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
append_newline: true
marker_begin: "BEGIN FTP GROUP SSH MATCH BLOCK"
create: true
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t %s"
state: present
loop: "{{ ftp_groups.results }}"
register: configured_sftp

View File

@@ -1,443 +0,0 @@
#SPDX-License-Identifier: MIT-0
---
# tasks file for roles/init-vps
# @NOTE server deployment method is based on task tags compiled herein
# @TODO review 'loop' task attribute return values and make compliant changes
- name: Finding SSH public keys for root
ansible.builtin.find:
paths: "{{ cnode_homedir | default('/home/' ~ ansible_user ~ '/.ssh') }}" # @TODO define 'cnode_homedir' in playbook
patterns: "{{ ['^'] | product(keys) | map('join') | list }}"
file_type: file
use_regex: true
register: ssh_keypairs
- name: Bootstrapping VPS
block:
- name: Creating VPS via Linode VPS service API
block:
- name: Creating the VPS
linode.cloud.instance:
api_token: "{{ token }}"
label: "{{ instance }}"
type: g6-standard-2
image: "{{ operating_system }}"
disk_encryption: enabled
region: "{{ origin }}"
private_ip: true
root_pass: "{{ password }}"
authorized_keys: "{{ ssh_keypairs.files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | map('lookup', 'file') | list }}"
state: present
register: new_instance
- name: Waiting for that VPS to come online
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
delegate_facts: true
ansible.builtin.wait_for_connection:
delay: 20
timeout: 300
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
- name: Checking if that VPS has required operating system
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
delegate_facts: true
when: ansible_facts["system"] != "Linux"
ansible.builtin.fail:
msg: Unsupported operating system found
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
- name: Checking if that VPS has required Linux distro
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
delegate_facts: true
when: ansible_facts["system"] == "Linux" and ansible_facts["os_family"] != "Debian"
ansible.builtin.fail:
msg: Unsupported Linux distro found
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
tags:
- linode
tags:
- vps
- name: Bootstrapping homeserver
block:
- name: Installing operating system or distro in server
when: operating_system is defined
block:
- name: Creating a server
block: []
tags:
- unimplemented
- name: Waiting for that VPS to come online
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
delegate_facts: true
ansible.builtin.wait_for_connection:
delay: 20
timeout: 300
vars:
ansible_user: root
- name: Checking if that server has required operating system
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
delegate_facts: true
when: ansible_facts["system"] != "Linux"
ansible.builtin.fail:
msg: Unsupported operating system found
vars:
ansible_user: root
- name: Checking if that server has required Linux distro
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
delegate_facts: true
when: ansible_facts["system"] == "Linux" and ansible_facts["os_family"] != "Debian"
ansible.builtin.fail:
msg: Unsupported Linux distro found
vars:
ansible_user: root
- name: Providing authorized keys for server root account
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
delegate_facts: true
ansible.posix.authorized_key:
user: "{{ ansible_facts['user_id'] }}"
key: "{{ lookup('file', item) }}"
state: present
vars:
ansible_root: root
loop: "{{ ssh_keypairs.files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | list }}"
tags:
- lan
- name: Starting SSH hardening
when: harden
delegate_facts: true
block:
- name: Hardening SSH service for the Linode VPS
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
ansible.builtin.copy:
src: sshd_config.d/harden.conf
dest: /etc/ssh/sshd_config.d/harden.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t %s"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
register: ssh_hardened
tags:
- linode
- name: Hardening SSH service for the server
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
ansible.builtin.copy:
src: sshd_config.d/harden.conf
dest: /etc/ssh/sshd_config.d/harden.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t %s"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
register: ssh_hardened
tags:
- lan
- name: Starting user and group creation for SSH access
block:
- name: Creating group remote for managing SSH access
delegate_facts: true
block:
- name: In the Linode VPS
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
ansible.builtin.group:
name: remote
system: true
state: present
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
register: remote_group
tags:
- linode
- name: In the server
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
ansible.builtin.group:
name: remote
system: true
state: present
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
register: remote_group
tags:
- lan
- name: Creating an administrative user
delegate_facts: true
when: ansible_facts["system"] == "Linux"
block:
- name: In the Linode VPS
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
ansible.builtin.user:
name: "{{ item.username }}"
comment: administrator
group: "{{ item.username }}"
groups:
- "{{ remote_group.name }}"
- sudo # @NOTE used by Debian
append: true
generate_ssh_key: true
create_home: true
password: "{{ item.password }}"
shell: "/bin/bash"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
loop: "{{ admins }}"
register: admin_users
tags:
- linode
- name: In the server
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
ansible.builtin.user:
name: "{{ item.username }}"
comment: administrator
group: "{{ item.username }}"
groups:
- "{{ remote_group.name }}"
- sudo # @NOTE used by Debian
append: true
generate_ssh_key: true
create_home: true
password: "{{ item.password }}"
shell: "/bin/bash"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
loop: "{{ admins }}"
register: admin_users
tags:
- lan
- name: Finding SSH public keys for an administrative user
when: item.username in (admin_users.results | map(attribute="name") | list)
ansible.builtin.find:
paths: "{{ cnode_homedir | default('/home/' ~ ansible_user ~ '/.ssh') }}" # @TODO define 'cnode_homedir' in playbook
patterns: "{{ ['^'] | product(item.keys) | map('join') | list }}"
file_type: file
use_regex: true
loop: "{{ admins }}"
register: admin_ssh_keypairs
- name: Authorizing SSH public key for an administrative user
delegate_facts: true
block:
- name: In the Linode VPS
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
ansible.posix.authorized_key:
user: "{{ admin_users.results[idx] }}"
key: "{{ admin_ssh_keypairs.results[idx].files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | map('lookup', 'file') | list | map('join','\n') }}"
state: present
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
loop: "{{ admin_users.results }}"
loop_control:
index_var: idx
register: ssh_authorizations
tags:
- linode
- name: In the server
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
ansible.posix.authorized_key:
user: "{{ admin_users.results[idx] }}"
key: "{{ admin_ssh_keypairs.results[idx].files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | map('lookup', 'file') | list | map('join','\n') }}"
state: present
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
loop: "{{ admin_users.results }}"
loop_control:
index_var: idx
register: ssh_authorizations
tags:
- lan
- name: Allowing sole SSH access to users in group remote
delegate_facts: true
block:
- name: In Linode VPS
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
ansible.builtin.template:
src: sshd_config.d/allowance.conf.j2 # @TODO create corresponding role template file
dest: /etc/ssh/sshd_config.d/allowance.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t %s"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
register: ssh_gatekept
tags:
- linode
- name: In Linode VPS
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
ansible.builtin.template:
src: sshd_config.d/allowance.conf.j2 # @TODO create corresponding role template file
dest: /etc/ssh/sshd_config.d/allowance.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t %s"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
register: ssh_gatekept
tags:
- lan
- name: Setting approved SSH authentication procedures
when: harden
delegate_facts: true
block:
- name: In the Linode VPS
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
ansible.builtin.copy:
src: sshd_config.d/auth.conf
dest: /etc/ssh/sshd_config.d/auth.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t %s"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
register: ssh_authenticator
tags:
- linode
- name: In the server
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
ansible.builtin.copy:
src: sshd_config.d/auth.conf
dest: /etc/ssh/sshd_config.d/auth.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t %s"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
register: ssh_authenticator
tags:
- lan
- name: Installing core packages
delegate_facts: true
block:
- name: In the Linode VPS
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
block:
- name: Registering a package signing key
when: ansible_facts["os_family"] == "Debian" and item.key is defined and item.key_path is defined
ansible.builtin.get_url:
url: "{{ item.key }}"
dest: "{{ item.key_path | default('/etc/apt/keyrings/') }}"
owner: root
group: root
mode: "644"
force: true
backup: true
loop: "{{ pkgs.mngr.core | rejectattr('key', 'search', '\\.deb$') }}"
- name: Installing a package signing key
when: ansible_facts["os_family"] == "Debian" and item.key is defined
ansible.builtin.apt:
deb: "{{ item.key }}"
state: latest
loop: "{{ pkgs.mngr.core | selectattr('key', 'search', '\\.deb$') }}"
- name: Registering a package source
when: ansible_facts["os_family"] == "Debian" and item.src_entry is defined and item.src_path is defined
ansible.builtin.copy:
content: "{{ item.src_entry }}"
dest: "{{ item.src_path }}"
owner: root
group: root
mode: "644"
force: true
backup: true
loop: "{{ pkgs.mngr.core }}"
- name: Installing a local package in managed node
when: ansible_facts["os_family"] == "Debian" and item.uri is defined
ansible.builtin.apt:
deb: "{{ item.uri }}"
update_cache: true
state: latest
loop: "{{ pkgs.mngr.core | selectattr('uri', 'search', '\\.deb$') }}"
- name: Installing a package
when: item.name is defined and item.uri is undefined
ansible.builtin.package:
name: "{{ item.name }}"
update_cache: true
state: latest
notify: "{{ item.name }}" # @TODO create corresponding roles/init-vps handlers
loop: "{{ pkgs.mngr.core| rejectattr('uri', 'search', '\\.deb$') }}"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
tags:
- linode
- name: In the server
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
block:
- name: Registering a package signing key
when: ansible_facts["os_family"] == "Debian" and item.key is defined and item.key_path is defined
ansible.builtin.get_url:
url: "{{ item.key }}"
dest: "{{ item.key_path | default('/etc/apt/keyrings/') }}"
owner: root
group: root
mode: "644"
force: true
backup: true
loop: "{{ pkgs.mngr.core | rejectattr('key', 'search', '\\.deb$') }}"
- name: Installing a package signing key
when: ansible_facts["os_family"] == "Debian" and item.key is defined
ansible.builtin.apt:
deb: "{{ item.key }}"
state: latest
loop: "{{ pkgs.mngr.core | selectattr('key', 'search', '\\.deb$') }}"
- name: Registering a package source
when: ansible_facts["os_family"] == "Debian" and item.src_entry is defined and item.src_path is defined
ansible.builtin.copy:
content: "{{ item.src_entry }}"
dest: "{{ item.src_path }}"
owner: root
group: root
mode: "644"
force: true
backup: true
loop: "{{ pkgs.mngr.core }}"
- name: Installing a local package in managed node
when: ansible_facts["os_family"] == "Debian" and item.uri is defined
ansible.builtin.apt:
deb: "{{ item.uri }}"
update_cache: true
state: latest
notify: "{{ item.name }}"
loop: "{{ pkgs.mngr.core | selectattr('uri', 'search', '\\.deb$') }}"
- name: Installing a package
when: item.name is defined and item.uri is undefined
ansible.builtin.package:
name: "{{ item.name }}"
update_cache: true
state: latest
notify: "{{ item.name }}" # @TODO create corresponding roles/init-vps handlers
loop: "{{ pkgs.mngr.core| rejectattr('uri', 'search', '\\.deb$') }}"
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
tags:
- lan
tags:
- get_pkgs

View File

@@ -0,0 +1,91 @@
#SPDX-License-Identifier: MIT-0
---
# tasks file for roles/init-vps
# @NOTE server deployment method is based on task tags compiled herein
# @TODO review 'loop' task attribute return values and make compliant changes
- name: Finding SSH public keys for root
ansible.builtin.find:
paths: "{{ cnode_homedir | default('/home/' ~ ansible_user ~ '/.ssh') }}" # @TODO define 'cnode_homedir' in playbook
patterns: "{{ ['^'] | product(keys) | map('join') | list }}"
file_type: file
use_regex: true
register: ssh_keypairs
- name: Bootstrapping VPS
block:
- name: Creating VPS via Linode VPS service API
block:
- name: Creating the VPS
linode.cloud.instance:
api_token: "{{ token }}"
label: "{{ instance }}"
type: g6-standard-2
image: "{{ operating_system }}"
disk_encryption: enabled
region: "{{ origin }}"
private_ip: true
root_pass: "{{ password }}"
authorized_keys: "{{ ssh_keypairs.files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | map('lookup', 'file') | list }}"
state: present
register: new_instance
- name: Waiting for that VPS to come online
delegate_to: "{{ new_instance.instance[ip_pref][0] }}"
delegate_facts: true
ansible.builtin.wait_for_connection:
delay: 20
timeout: 300
vars:
ansible_ssh_private_key_file: "{{ chosen_privkey | default(ssh_keypairs.files | rejectattr('path', 'search', '\\.pub$') | map(attribute='path') | list | random) }}" # @TODO define 'chosen_privkey'in playbook
ansible_user: root
tags:
- linode
tags:
- vps
- name: Bootstrapping homeserver
block:
- name: Installing operating system or distro in server
when: operating_system != None
block:
- name: Creating a server
block: []
tags:
- unimplemented
- name: Waiting for that server to come online
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
delegate_facts: true
remote_user: root
ansible.builtin.wait_for_connection:
delay: 20
timeout: 300
vars:
ansible_user: root
- name: Checking if that server has required operating system
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
delegate_facts: true
remote_user: root
when: ansible_facts["system"] != "Linux"
ansible.builtin.fail:
msg: Unsupported operating system found
vars:
ansible_user: root
- name: Checking if that server has required Linux distro
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
delegate_facts: true
remote_user: root
when: ansible_facts["system"] == "Linux" and ansible_facts["os_family"] != "Debian"
ansible.builtin.fail:
msg: Unsupported Linux distro found
vars:
ansible_user: root
- name: Providing authorized keys for server root account
delegate_to: "{{ hostvars[instance]['ansible_default_' ~ ip_pref].address }}"
delegate_facts: true
remote_user: root
ansible.posix.authorized_key:
user: "{{ ansible_user }}"
key: "{{ lookup('file', item) }}"
state: present
vars:
ansible_root: root
loop: "{{ ssh_keypairs.files | selectattr('path', 'search', '\\.pub$') | map(attribute='path') | list }}"
tags:
- lan

View File

@@ -0,0 +1,93 @@
#SPDX-License-Identifier: MIT-0
---
# tasks file for roles/init-vps
- name: Checking whether administrative or root login used
when: ansible_user not in (admins | map(attribute="username") | list) and ansible_user != "root"
ansible.builtin.fail:
msg: Must use administrative or root user for subsequent tasks
- name: Starting user and group creation for SSH access
block:
- name: Creating group remote for managing SSH access
become: true
ansible.builtin.group:
name: remote
system: true
state: present
register: remote_group
tags:
- lan
- name: Creating an administrative user
become: true
ansible.builtin.user:
name: "{{ item.username }}"
comment: administrator
groups:
- "{{ remote_group.name | default('remote') }}"
- sudo # @NOTE used by Debian
append: true
generate_ssh_key: true
create_home: true
password: "{{ item.password }}"
shell: "/bin/bash"
loop: "{{ admins }}"
register: admin_users
tags:
- lan
- name: Finding SSH public keys for an administrative user
delegate_facts: true
delegate_to: localhost
when: item.username in (admin_users.results | map(attribute="name") | list)
ansible.builtin.find:
paths: "{{ local_facts['user_dir'] }}/.ssh" # @TODO define 'cnode_homedir' in playbook
patterns: "{{ ['^'] | product(item.ssh_keys) | map('join') | list }}"
file_type: file
use_regex: true
loop: "{{ admins }}"
register: admin_ssh_keypairs
- name: Creating list wherein each SSH public key is associated with a user
ansible.builtin.set_fact:
pubkey_users: "{{ [admin_users.results[idx].name] | product(admin_ssh_keypairs.results[idx].files | selectattr('path', 'search', '\\.pub$') | map(attribute='path')) }}"
loop: "{{ admins }}"
loop_control:
index_var: idx
- name: Authorizing SSH public key for an administrative user
become: true
ansible.posix.authorized_key:
user: "{{ item[0] }}"
key: "{{ lookup('file', item[1]) }}"
state: present
loop: "{{ pubkey_users }}"
register: ssh_authorizations
tags:
- lan
- name: Setting approved SSH authentication procedures
when: harden and ansible_facts["system"] == "Linux"
become: true
ansible.builtin.copy:
src: sshd_config.d/auth.conf
dest: /etc/ssh/sshd_config.d/auth.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t -f %s"
register: ssh_authenticator
tags:
- lan
- ssh_secure_auth
- name: Allowing sole SSH access to users in group remote
when: ansible_facts["system"] == "Linux"
become: true
ansible.builtin.template:
src: sshd_config.d/allowance.conf.j2 # @TODO create corresponding role template file
dest: /etc/ssh/sshd_config.d/allowance.conf
owner: root
group: root
mode: "644"
force: true
backup: true
validate: "sshd -t -f %s"
register: ssh_gatekept
tags:
- lan

View File

@@ -1,9 +0,0 @@
{% for item in ftp_groups.results %}
Match Group {{ item.name }}
ForceCommand internal-sftp -d /%u
ChrootDirectory /srv/{{ item.name}}
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
{% endfor %}