commit 9391b3344a1bda6221a8b24d20de6f825e114a6a Author: Sven Velt Date: Wed Nov 20 11:26:22 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e61d48 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +group_vars/all.yml +hosts.* +.cache + +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# ---> Ansible +*.retry +.facts + +# ---> Vim +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..86db147 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# lxc_create + +Create and configure LXContainers defined in inventory + + +## Requirements + +- Ansible: + - Collection `community.general` for LXC modules +- Python: + - Bindings for LXC +- External plugins: + - `lxc_ssh` connection plugin from https://github.com/andreasscherbaum/ansible-lxc-ssh/ + + +## Role Variables + +- Required + - `ssh_keys`: List of SSH keys for root +- Optional + - `list_of_lxc_hosts`: unset default is `groups.lxc_host` (inventory group "lxc_hosts") + - `root_password`: unset, default is `root` + - `service_username`: unset, if defined user will be created + - `service_password`: unset, default is same as `service_username` + - `service_ssh_keys`: unset, default is same as `ssh_keys` (for root) + + +## Dependencies + +- Host must be prepared for LXContainers + +## Example Playbook + + - hosts: + - lxc_containers + roles: + - lxc_create + +## License + +AGPL3.0-or-later + +## Author Information + +- Sven Velt +- https://git.velt.biz/ + diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..7ac12f7 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart SSH + service: + name: "{{ ssh_service_name[ansible_os_family|lower]|default('sshd') }}" + state: restarted + + diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..d4b9026 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,21 @@ +--- +dependencies: [] + +galaxy_info: + author: Sven Velt + description: Create LXContainers from inventory + company: velt.biz + issue_tracker_url: https://git.velt.biz/Ansible/role.lxc_create/issues + license: AGPL-3.0-or-later + min_ansible_version: 2.1 + platforms: + - name: Alpine + - name: Debian + - name: Ubuntu + - name: Fedora + - name: EL + - name: SUSE + + galaxy_tags: + - operations + diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..836de49 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,186 @@ +--- +- name: Sanity checks + assert: + that: + - inventory_hostname not in list_of_lxc_hosts|default(groups.lxc_hosts) + - ssh_keys is defined and ssh_keys is iterable and ssh_keys != [] + - service_user is not defined or service_user|regex_search('^[a-z][a-z0-9-]+$') + + +- name: Check for no-validate parameter in download template + shell: + cmd: "/usr/share/lxc/templates/lxc-download --help | grep no-validate || true" + changed_when: false + register: lxc_download_validate + delegate_to: "{{ ansible_host|default('localhost') }}" + + +- name: Get architecture + shell: "uname -m || true" + changed_when: false + register: lxc_host_arch_native + delegate_to: "{{ ansible_host|default('localhost') }}" + + +- name: Create LXContainer + lxc_container: + name: "{{ inventory_hostname }}" + state: started + template: download + template_options: "-a {{ architecture_mapping[lxc_host_arch_native.stdout] }} -d {{ os_d }} -r {{ os_r }} {% if 'no-validate' in lxc_download_validate.stdout %}--no-validate{% endif %}" + config: "{{ lxc_config_file|default('/etc/lxc/ansible.conf') }}" + container_config: + - "lxc.group = {{ os_d }}" + - "lxc.group = {{ (cmdline_python[os_d][0]).split(' ')[0].split('-')[0] }}" + register: lxc_created + delegate_to: "{{ ansible_host|default('localhost') }}" + + +- pause: + seconds: 10 + when: lxc_created is changed + + +- name: Check for default route + raw: "ip route | grep default" + changed_when: False + + +- name: Raw-Install Python3 + raw: "{{ item }}" + loop: "{{ cmdline_python[os_d] }}" + when: use_python2 != True + + +- name: Raw-Install Python2 + raw: "{{ item }}" + loop: "{{ cmdline_python2[os_d] }}" + when: use_python2 == True + + +- name: OS-dependent fixes + raw: "{{ item }}" + loop: "{{ cmdline_fixes[os_d][os_r]|default([]) }}" + + +- setup: + + +- name: Set root password + shell: + cmd: "echo root:{{ root_password|default('root') }} | chpasswd -c SHA256" + + +- name: Add SSH keys + block: + - name: "1st try: authorized_key module" + authorized_key: + user: root + key: "{{ item }}" + loop: "{{ ssh_keys }}" + rescue: + - name: "2nd try: create ~/.ssh directory" + file: + path: /root/.ssh/ + state: directory + owner: root + group: root + mode: 0700 + - name: "2nd try: add key via lineinfile module" + lineinfile: + path: /root/.ssh/authorized_keys + line: "{{ item }}" + owner: root + group: root + mode: 0600 + create: yes + backup: yes + loop: "{{ ssh_keys }}" + + +- name: "[BLOCK] when 'service_username' is set" + when: service_username is defined + block: + + - name: 'Add normal user "{{ service_username }}"' + user: + name: "{{ service_username }}" + shell: "{{ user_shell[os_d]|default('/bin/bash') }}" + + - name: 'Set password for user "{{ service_username }}"' + shell: + cmd: "echo {{ service_username }}:{{ service_password|default(service_username) }} | chpasswd -c SHA256" + + - name: Add SSH keys + block: + - name: "1st try: authorized_key module" + authorized_key: + user: "{{ service_username }}" + key: "{{ item }}" + loop: "{{ ssh_keys_service|default(ssh_keys) }}" + rescue: + - name: "2nd try: get homedir of user" + getent: + database: passwd + key: "{{ service_username }}" + split: ":" + - name: "2nd try: create ~/.ssh directory" + file: + path: "{{ getent_passwd[service_username][4] }}/.ssh/" + state: directory + owner: "{{ service_username }}" + group: "{{ service_username }}" + mode: 0700 + - name: "2nd try: add key via lineinfile module" + lineinfile: + path: "{{ getent_passwd[service_username][4] }}/.ssh/authorized_keys" + line: "{{ item }}" + owner: "{{ service_username }}" + group: "{{ service_username }}" + mode: 0600 + create: yes + backup: yes + loop: "{{ ssh_keys }}" + + - name: Install sudo + package: + name: sudo + + - name: Add sudo line for service + lineinfile: + path: "/etc/sudoers.d/{{ service_username }}" + regexp: "^service" + line: "{{ service_username }} ALL=(ALL:ALL) NOPASSWD: ALL" + create: yes + backup: yes + + +- name: Install SSH + package: + name: "{{ ssh_package_name[ansible_os_family|lower]|default('openssh-server') }}" + state: latest + + +- name: "Set «PermitRootLogin» to «yes»" + lineinfile: + dest: "{{ ssh_config_filename[os_d][os_r]|default('/etc/ssh/sshd_config') }}" # FIXME + regexp: '^[#\s]*PermitRootLogin' + line: "PermitRootLogin yes" + create: yes + backup: yes + notify: "Restart SSH" + + +- name: Enable SSH + service: + name: "{{ ssh_service_name[ansible_os_family|lower]|default('sshd') }}" + enabled: yes + + +- name: Start SSH + service: + name: "{{ ssh_service_name[ansible_os_family|lower]|default('sshd') }}" + state: started + ignore_errors: yes + + diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..bcf83a6 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,79 @@ +--- +### vars file for roles/lxc_create + +# architecture mapping from "uname -m" to package +architecture_mapping: + aarch64: arm64 + x86_64: amd64 + #ppc64le: ppc64el + #s390x: s390x + #armv7l: armhf + + +# Python3 installaton, "*cmdline_" must be listed AFTER "^cmdline_"! +cmdline_python: + alpine: + - "apk add -U python3" + archlinux: + - "pacman -Sy" + - "pacman -S --noconfirm python" + centos: &cmdline_python_centos + - "yum clean all" + - "yum makecache" + - "yum install -y python3 || true" + almalinux: *cmdline_python_centos + fedora: *cmdline_python_centos + oracle: *cmdline_python_centos + rockylinux: *cmdline_python_centos + debian: &cmdline_python_debian + - "apt-get -y update" + - "apt-get install -y python3 python3-apt" + devuan: *cmdline_python_debian + mint: *cmdline_python_debian + ubuntu: *cmdline_python_debian + opensuse: + - "zypper --gpg-auto-import-keys --no-gpg-checks -n refresh" + - "zypper --gpg-auto-import-keys --no-gpg-checks -n install python3" + voidlinux: + - "xbps-install -Suy python3 libgcc" + + +# Python2 installation +cmdline_python2: + debian: &cmdline_python2_debian + - "apt-get -y update" + - "apt-get install -y python python-apt" + ubuntu: *cmdline_python2_debian + + +# Fixes for some OS/Distri +cmdline_fixes: + oracle: + 8: + - '[ ! -f /usr/bin/python3 ] && ln -s /usr/libexec/platform-python /usr/bin/python3 || true' + + +# SSH package name if not "openssh-server" +ssh_package_name: + alpine: openssh + archlinux: openssh + suse: openssh + void: openssh + + +# SSH config file if not "/etc/ssh/sshd_config" +ssh_config_filename: + opensuse: + tumbleweed: /etc/ssh/sshd_config.d/permitrootlogin.conf + + +# SSH service name if not "sshd" +ssh_service_name: + debian: ssh + + +# Shell for service user if not "/bin/bash" +user_shell: + alpine: /bin/ash + +