From a2a5ba3a5eae9ce9465664208664c62271d9411f Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 12:58:32 +0500 Subject: [PATCH 01/37] Changes LXC deployment role and introduces a new playbook for that --- .../deploy_and_set_up_lxc_on_proxmox.yaml | 11 +++++++++ playbooks/maintain.yaml | 23 ++++++++++++++++--- .../tasks/main.yaml | 17 +++++++------- 3 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 playbooks/deploy_and_set_up_lxc_on_proxmox.yaml rename roles/{basic_proxmox_lxc => deploy_lxc_on_proxmox}/tasks/main.yaml (69%) diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml new file mode 100644 index 0000000..bd8d31a --- /dev/null +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -0,0 +1,11 @@ +--- +- name: Deploy and set up an LXC container in Proxmox + hosts: all + remote_user: ansible + roles: + - ../roles/deploy_lxc_on_proxmox + vars_files: + ../inventory/group_vars/all/secrets.yaml + vars: + ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" + user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" diff --git a/playbooks/maintain.yaml b/playbooks/maintain.yaml index 368befe..aff6146 100644 --- a/playbooks/maintain.yaml +++ b/playbooks/maintain.yaml @@ -2,11 +2,28 @@ - name: Physical machines maintanance play hosts: all remote_user: ansible - roles: - - ../roles/common_healthcheck + vars_files: ../inventory/group_vars/all/secrets.yaml vars: ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" - ha_addr: https://ha.lan + + vars_prompt: + - name: lxc_hostname + prompt: "Hostname for the container:" + private: false + + - name: lxc_root_password + prompt: "Root password:" + private: true + confirm: true + encrypt: sha512_crypt + + - name: lxc_ip_address + prompt: "IP address for the container (in x.x.x.x/x formant):" + private: false + + + roles: + - ../roles/deploy_lxc_on_proxmox diff --git a/roles/basic_proxmox_lxc/tasks/main.yaml b/roles/deploy_lxc_on_proxmox/tasks/main.yaml similarity index 69% rename from roles/basic_proxmox_lxc/tasks/main.yaml rename to roles/deploy_lxc_on_proxmox/tasks/main.yaml index f5a96d6..1a2bf10 100644 --- a/roles/basic_proxmox_lxc/tasks/main.yaml +++ b/roles/deploy_lxc_on_proxmox/tasks/main.yaml @@ -1,6 +1,6 @@ --- -- name: Create new container with minimal options defining network interface with static ip +- name: Create a new container with minimal options defining network interface with static ip become: false community.proxmox.proxmox: validate_certs: false @@ -11,20 +11,21 @@ api_token_secret: "{{ proxmox_token_secret }}" cmode: "shell" - vmid: 1040 - password: ansible-test - hostname: "ansible-test" - # ostemplate: 'main:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst' + password: "{{ lxc_root_password }}" + hostname: "{{ lxc_hostname }}" ostemplate: 'main:vztmpl/debian-13-golden-image.tar.gz' memory: 2048 - cores: 5 + cores: 2 state: present disk_volume: - size: 2 # 2GB rootfs + # This is minimal requirement for rootfs + # It might be resized later + size: 4 # 4GB rootfs + # We put rootfs on fast local storage (NVMe SSD) storage: local pubkey: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}" netif: - net0: "name=eth0,gw=192.168.0.1,ip=192.168.0.40/24,bridge=vmbr0" + net0: "name=eth0,gw=192.168.0.1,ip={{ lxc_ip_address }},bridge=vmbr0" - name: Make sure the container has started become: false -- 2.52.0 From 84af7b011bb790577595cb8e8df05d7c319c18ef Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 13:44:56 +0400 Subject: [PATCH 02/37] FIX: fix the issue with the misdiscovery of the python interpreter when localhost is the target --- inventory/localhost.yaml | 7 ++++++ .../deploy_and_set_up_lxc_on_proxmox.yaml | 25 ++++++++++++++++--- playbooks/maintain.yaml | 24 +++--------------- requirements.txt | 2 ++ roles/deploy_lxc_on_proxmox/tasks/main.yaml | 3 +-- 5 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 inventory/localhost.yaml diff --git a/inventory/localhost.yaml b/inventory/localhost.yaml new file mode 100644 index 0000000..b71186d --- /dev/null +++ b/inventory/localhost.yaml @@ -0,0 +1,7 @@ +--- +physical: + hosts: + localhost: + ansible_become_password: "{{ ansible_become_passwd }}" + ansible_connection: local + ansible_python_interpreter: "{{ ansible_playbook_python }}" diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml index bd8d31a..a14f201 100644 --- a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -1,11 +1,30 @@ --- -- name: Deploy and set up an LXC container in Proxmox +- name: Physical machines maintanance play hosts: all remote_user: ansible - roles: - - ../roles/deploy_lxc_on_proxmox + gather_facts: false + vars_files: ../inventory/group_vars/all/secrets.yaml vars: ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" + + vars_prompt: + - name: lxc_hostname + prompt: "Hostname for the container:" + private: false + + - name: lxc_root_password + prompt: "Root password:" + private: true + confirm: true + encrypt: sha512_crypt + + - name: lxc_ip_address + prompt: "IP address for the container (in x.x.x.x/x formant):" + private: false + + + roles: + - ../roles/deploy_lxc_on_proxmox diff --git a/playbooks/maintain.yaml b/playbooks/maintain.yaml index aff6146..bd8d31a 100644 --- a/playbooks/maintain.yaml +++ b/playbooks/maintain.yaml @@ -1,29 +1,11 @@ --- -- name: Physical machines maintanance play +- name: Deploy and set up an LXC container in Proxmox hosts: all remote_user: ansible - + roles: + - ../roles/deploy_lxc_on_proxmox vars_files: ../inventory/group_vars/all/secrets.yaml vars: ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" - - vars_prompt: - - name: lxc_hostname - prompt: "Hostname for the container:" - private: false - - - name: lxc_root_password - prompt: "Root password:" - private: true - confirm: true - encrypt: sha512_crypt - - - name: lxc_ip_address - prompt: "IP address for the container (in x.x.x.x/x formant):" - private: false - - - roles: - - ../roles/deploy_lxc_on_proxmox diff --git a/requirements.txt b/requirements.txt index 1d11f18..bb5d472 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,10 +17,12 @@ mypy_extensions==1.1.0 packaging==26.2 pathspec==1.1.1 platformdirs==4.10.0 +proxmoxer>=2.3 pycparser==3.0 pytokens==0.4.1 PyYAML==6.0.3 referencing==0.37.0 +requests>=2.34.2 resolvelib==1.2.1 rpds-py==2026.6.3 ruamel.yaml==0.19.1 diff --git a/roles/deploy_lxc_on_proxmox/tasks/main.yaml b/roles/deploy_lxc_on_proxmox/tasks/main.yaml index 1a2bf10..f230a41 100644 --- a/roles/deploy_lxc_on_proxmox/tasks/main.yaml +++ b/roles/deploy_lxc_on_proxmox/tasks/main.yaml @@ -37,12 +37,11 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - vmid: 1040 + hostname: "{{ lxc_hostname }}" state: started - name: Sleep for a minute to ensure that ssh is ready become: false ansible.builtin.wait_for: timeout: 60 - delegate_to: localhost changed_when: false -- 2.52.0 From d0c272ff832fd13540bb95b5e7e0322993e27045 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 14:10:53 +0400 Subject: [PATCH 03/37] REFACTOR: VM creation and setup --- .../deploy_and_set_up_lxc_on_proxmox.yaml | 6 ++--- playbooks/deploy_vm_from_cloud_init.yaml | 23 +++++-------------- .../tasks/main.yaml | 20 ++++++++-------- 3 files changed, 19 insertions(+), 30 deletions(-) rename roles/{basic_proxmox_vm => deploy_vm_on_proxmox}/tasks/main.yaml (86%) diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml index a14f201..2530724 100644 --- a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -12,17 +12,17 @@ vars_prompt: - name: lxc_hostname - prompt: "Hostname for the container:" + prompt: "Hostname for the container" private: false - name: lxc_root_password - prompt: "Root password:" + prompt: "Root password" private: true confirm: true encrypt: sha512_crypt - name: lxc_ip_address - prompt: "IP address for the container (in x.x.x.x/x formant):" + prompt: "IP address for the container (in x.x.x.x/x formant)" private: false diff --git a/playbooks/deploy_vm_from_cloud_init.yaml b/playbooks/deploy_vm_from_cloud_init.yaml index 96c045c..f432053 100644 --- a/playbooks/deploy_vm_from_cloud_init.yaml +++ b/playbooks/deploy_vm_from_cloud_init.yaml @@ -2,27 +2,16 @@ - name: Deploy a virtual machine from a cloud init image hosts: localhost gather_facts: false - roles: - - ../roles/basic_proxmox_vm -- name: Basic Proxmox guest deployment - hosts: all - remote_user: ansible - roles: - - ../roles/basic_postinstall vars_files: ../inventory/group_vars/all/secrets.yaml vars: ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" + vars_prompt: + - name: vm_hostname + prompt: "Hostname for the VM" + private: false - handlers: - - name: restart ssh-server - service: - name: ssh - state: restarted - - - name: restart sshd-server - service: - name: sshd - state: restarted \ No newline at end of file + roles: + - ../roles/deploy_vm_on_proxmox diff --git a/roles/basic_proxmox_vm/tasks/main.yaml b/roles/deploy_vm_on_proxmox/tasks/main.yaml similarity index 86% rename from roles/basic_proxmox_vm/tasks/main.yaml rename to roles/deploy_vm_on_proxmox/tasks/main.yaml index 990fec3..392bdfd 100644 --- a/roles/basic_proxmox_vm/tasks/main.yaml +++ b/roles/deploy_vm_on_proxmox/tasks/main.yaml @@ -10,13 +10,19 @@ api_token_secret: "{{ proxmox_token_secret }}" clone: debian-13-cloud-init-template - newid: 1041 - name: ansible-ci-test + name: "{{ vm_hostname }}" storage: main format: qcow2 full: true timeout: 500 +- name: Sleep for 20 seconds to ensure that Proxmox has cloned the VM + become: false + ansible.builtin.wait_for: + timeout: 20 + delegate_to: localhost + changed_when: false + - name: Tweak the hardware settings become: false community.proxmox.proxmox_kvm: @@ -27,7 +33,7 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - vmid: 1041 + name: "{{ vm_hostname }}" cores: 4 memory: 4096 # Minimal value for debian based OS for smooth workflow update: true @@ -42,12 +48,6 @@ api_token_id: ansible api_token_secret: "{{ proxmox_token_secret }}" - vmid: 1041 + name: "{{ vm_hostname }}" state: started -- name: Sleep for 3 minutes to ensure that cloud-init's done its thing - become: false - ansible.builtin.wait_for: - timeout: 180 - delegate_to: localhost - changed_when: false -- 2.52.0 From f5670addc658fb955711bc5df264b47c7275754d Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 17:27:42 +0400 Subject: [PATCH 04/37] REFACTOR: remove useless playbook, move creation of Ansible user into a separated role --- playbooks/deploy_lxc.yaml | 17 -------- roles/configure_ansible_user/tasks/main.yaml | 41 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 17 deletions(-) delete mode 100644 playbooks/deploy_lxc.yaml create mode 100644 roles/configure_ansible_user/tasks/main.yaml diff --git a/playbooks/deploy_lxc.yaml b/playbooks/deploy_lxc.yaml deleted file mode 100644 index b492713..0000000 --- a/playbooks/deploy_lxc.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -- name: Deplot simple test LXC on Proxmox VE - hosts: localhost - gather_facts: false - roles: - - ../roles/basic_proxmox_lxc - -- name: Basic Proxmox guest deployment - hosts: all - remote_user: root - roles: - - ../roles/basic_postinstall - vars_files: - ../inventory/group_vars/all/secrets.yaml - vars: - ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" - user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" \ No newline at end of file diff --git a/roles/configure_ansible_user/tasks/main.yaml b/roles/configure_ansible_user/tasks/main.yaml new file mode 100644 index 0000000..2097ae9 --- /dev/null +++ b/roles/configure_ansible_user/tasks/main.yaml @@ -0,0 +1,41 @@ +--- +## Installing packages +- name: Install sudo on apt systems + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + name: + - sudo + update-cache: true + +# The same commands for Alpine +- name: Update and install packages on Alpine + when: (ansible_facts['distribution'] == "Alpine") + community.general.apk: + name: sudo + update_cache: true + remote_user: ansible + + +## Creating and setting up the ansible user +## First, create sshusers group to grant ssh access +- name: Ensure group "sshusers" exists + ansible.builtin.group: + name: sshusers + state: present + +## Add the user to sshusers (for ssh access) and sudo (gain root access) +- name: Create a new user with a password for Ansible + ansible.builtin.user: + name: ansible + password: "{{ ansible_user_passwd_hash }}" + + groups: sshusers,sudo + append: true + +## Since password authentication in SSH will be disabled, we need to add an authorized key +- name: Set authorized key taken from file + ansible.posix.authorized_key: + user: ansible + state: present + key: "{{ ansible_ssh_key }}" -- 2.52.0 From 05f0c1cc5f790d9876d719d6e40af72355927f71 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 17:30:17 +0400 Subject: [PATCH 05/37] REFACTOR: move SSH hardening into a separated role --- roles/harden_ssh/files/.vimrc | 16 ++++ roles/harden_ssh/files/.zshrc | 105 ++++++++++++++++++++++ roles/harden_ssh/files/hardened_ssh.conf | 4 + roles/harden_ssh/files/hardened_sshd.conf | 29 ++++++ roles/harden_ssh/tasks/main.yaml | 36 ++++++++ 5 files changed, 190 insertions(+) create mode 100644 roles/harden_ssh/files/.vimrc create mode 100644 roles/harden_ssh/files/.zshrc create mode 100644 roles/harden_ssh/files/hardened_ssh.conf create mode 100644 roles/harden_ssh/files/hardened_sshd.conf create mode 100644 roles/harden_ssh/tasks/main.yaml diff --git a/roles/harden_ssh/files/.vimrc b/roles/harden_ssh/files/.vimrc new file mode 100644 index 0000000..70d5a79 --- /dev/null +++ b/roles/harden_ssh/files/.vimrc @@ -0,0 +1,16 @@ +set number +set tabstop=2 +" Disable compatibility with vi which can cause unexpected issues. +set nocompatible + +" Enable type file detection. Vim will be able to try to detect the type of file in use. +filetype on + +" Enable plugins and load plugin for the detected file type. +filetype plugin on + +" Load an indent file for the detected file type. +filetype indent on + +" Turn syntax highlighting on. +syntax on diff --git a/roles/harden_ssh/files/.zshrc b/roles/harden_ssh/files/.zshrc new file mode 100644 index 0000000..4423d1f --- /dev/null +++ b/roles/harden_ssh/files/.zshrc @@ -0,0 +1,105 @@ +export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:/home/max/soft/gnu_linux:$PATH + +# Path to your Oh My Zsh installation. +export ZSH="$HOME/.oh-my-zsh" + +export GTK_THEME=Adwaita-dark + +# Set name of the theme to load --- if set to "random", it will +# load a random theme each time Oh My Zsh is loaded, in which case, +# to know which specific one was loaded, run: echo $RANDOM_THEME +# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes +ZSH_THEME="gnzh" + +# Set list of themes to pick from when loading at random +# Setting this variable when ZSH_THEME=random will cause zsh to load +# a theme from this variable instead of looking in $ZSH/themes/ +# If set to an empty array, this variable will have no effect. +# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" ) + +# Uncomment the following line to use case-sensitive completion. +# CASE_SENSITIVE="true" + +# Uncomment the following line to use hyphen-insensitive completion. +# Case-sensitive completion must be off. _ and - will be interchangeable. +# HYPHEN_INSENSITIVE="true" + +# Uncomment one of the following lines to change the auto-update behavior +# zstyle ':omz:update' mode disabled # disable automatic updates +# zstyle ':omz:update' mode auto # update automatically without asking +# zstyle ':omz:update' mode reminder # just remind me to update when it's time + +# Uncomment the following line to change how often to auto-update (in days). +# zstyle ':omz:update' frequency 13 + +# Uncomment the following line if pasting URLs and other text is messed up. +# DISABLE_MAGIC_FUNCTIONS="true" + +# Uncomment the following line to disable colors in ls. +# DISABLE_LS_COLORS="true" + +# Uncomment the following line to disable auto-setting terminal title. +# DISABLE_AUTO_TITLE="true" + +# Uncomment the following line to enable command auto-correction. +# ENABLE_CORRECTION="true" + +# Uncomment the following line to display red dots whilst waiting for completion. +# You can also set it to another string to have that shown instead of the default red dots. +# e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f" +# Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765) +# COMPLETION_WAITING_DOTS="true" + +# Uncomment the following line if you want to disable marking untracked files +# under VCS as dirty. This makes repository status check for large repositories +# much, much faster. +# DISABLE_UNTRACKED_FILES_DIRTY="true" + +# Uncomment the following line if you want to change the command execution time +# stamp shown in the history command output. +# You can set one of the optional three formats: +# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" +# or set a custom format using the strftime function format specifications, +# see 'man strftime' for details. +# HIST_STAMPS="mm/dd/yyyy" + +# Would you like to use another custom folder than $ZSH/custom? +# ZSH_CUSTOM=/path/to/new-custom-folder + +# Which plugins would you like to load? +# Standard plugins can be found in $ZSH/plugins/ +# Custom plugins may be added to $ZSH_CUSTOM/plugins/ +# Example format: plugins=(rails git textmate ruby lighthouse) +# Add wisely, as too many plugins slow down shell startup. +#plugins=(git) + +source $ZSH/oh-my-zsh.sh + +# User configuration + +# export MANPATH="/usr/local/man:$MANPATH" + +# You may need to manually set your language environment +# export LANG=en_US.UTF-8 + +# Preferred editor for local and remote sessions +# if [[ -n $SSH_CONNECTION ]]; then +# export EDITOR='vim' +# else +# export EDITOR='nvim' +# fi + +# Compilation flags +# export ARCHFLAGS="-arch $(uname -m)" + +# Set personal aliases, overriding those provided by Oh My Zsh libs, +# plugins, and themes. Aliases can be placed here, though Oh My Zsh +# users are encouraged to define aliases within a top-level file in +# the $ZSH_CUSTOM folder, with .zsh extension. Examples: +# - $ZSH_CUSTOM/aliases.zsh +# - $ZSH_CUSTOM/macos.zsh +# For a full list of active aliases, run `alias`. +# +# Example aliases +# alias zshconfig="mate ~/.zshrc" +# alias ohmyzsh="mate ~/.oh-my-zsh" diff --git a/roles/harden_ssh/files/hardened_ssh.conf b/roles/harden_ssh/files/hardened_ssh.conf new file mode 100644 index 0000000..012011c --- /dev/null +++ b/roles/harden_ssh/files/hardened_ssh.conf @@ -0,0 +1,4 @@ + Host * + HashKnownHosts yes + GSSAPIAuthentication yes + KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,curve25519-sha256 \ No newline at end of file diff --git a/roles/harden_ssh/files/hardened_sshd.conf b/roles/harden_ssh/files/hardened_sshd.conf new file mode 100644 index 0000000..21f4436 --- /dev/null +++ b/roles/harden_ssh/files/hardened_sshd.conf @@ -0,0 +1,29 @@ +PubkeyAuthentication yes +AuthorizedKeysFile .ssh/authorized_keys +PasswordAuthentication no +KbdInteractiveAuthentication no +UsePAM no +# Disable password authentication — keys only +PasswordAuthentication no +ChallengeResponseAuthentication no + + +AllowGroups sshusers +PrintMotd no +AcceptEnv LANG LC_* +ClientAliveCountMax 0 +ClientAliveInterval 300 +Port 22 + + +# Disable root login entirely +PermitRootLogin no + +# Limit authentication attempts +MaxAuthTries 3 +MaxSessions 3 + +# Use modern key exchange and ciphers, prioritize post-quantum algorithms (mlkem and sntrup) +KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org +Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com +MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com \ No newline at end of file diff --git a/roles/harden_ssh/tasks/main.yaml b/roles/harden_ssh/tasks/main.yaml new file mode 100644 index 0000000..a1486dd --- /dev/null +++ b/roles/harden_ssh/tasks/main.yaml @@ -0,0 +1,36 @@ +--- + +- name: Configure ssh-server daemon + ansible.builtin.copy: + src: "{{ role_path }}/files/hardened_sshd.conf" + dest: /etc/ssh/sshd_config.d/hardened_sshd.conf + mode: u=rw,g=r,o=r + when: ansible_facts['distribution'] == 'Ubuntu' + +- name: Configure ssh-server daemon + ansible.builtin.copy: + src: "{{ role_path }}/files/hardened_sshd.conf" + dest: /etc/ssh/sshd_config.d/hardened_sshd.conf + mode: u=rw,g=r,o=r + when: ansible_facts['distribution'] == 'Debian' + +- name: Configure ssh client + remote_user: ansible + ansible.builtin.copy: + src: "{{ role_path }}/files/hardened_ssh.conf" + dest: /etc/ssh/ssh_config.d/hardened_ssh.conf + mode: u=rw,g=r,o=r + +- name: Restart ssh-server Debian + remote_user: ansible + ansible.builtin.service: + name: sshd + state: restarted + when: ansible_facts['distribution'] == 'Debian' + +- name: Restart ssh-server Ubuntu + remote_user: ansible + ansible.builtin.service: + name: ssh + state: restarted + when: ansible_facts['distribution'] == 'Ubuntu' -- 2.52.0 From bd3cb7bfd174719a5b11aa47e0ce692af9357931 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 17:36:37 +0400 Subject: [PATCH 06/37] REFACTOR: move base system installation into a separated role --- roles/base_system/tasks/main.yaml | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 roles/base_system/tasks/main.yaml diff --git a/roles/base_system/tasks/main.yaml b/roles/base_system/tasks/main.yaml new file mode 100644 index 0000000..e462163 --- /dev/null +++ b/roles/base_system/tasks/main.yaml @@ -0,0 +1,43 @@ +--- +- name: Installing basic utils for comfort work (apt-based system) + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + name: + - vim + - neovim + - ranger + - zsh + - rsync + - git + - curl + - kitty + - unattended-upgrades + - ssh + - openssh-server + update-cache: true # Run apt update before installation + become: true + remote_user: ansible + +- name: Install qemu-guest-agent on VM + when: + - ansible_facts['os_family'] == "Debian" + - ansible_facts['virtualization_type'] == "kvm" + ansible.builtin.apt: + name: qemu-guest-agent + state: present + update-cache: true # Run apt update before installation + become: true + remote_user: ansible + tags: + - kvm-guests + - packages + + +# The same commands for Alpine +- name: Update and install packages on Alpine + when: (ansible_facts['distribution'] == "Alpine") + community.general.apk: + name: neovim vim ranger zsh rsync git curl kitty openssh + update_cache: true + remote_user: ansible -- 2.52.0 From 269aaec21c787308b35d954c1a5afe80d3643d11 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 17:39:38 +0400 Subject: [PATCH 07/37] REFACTOR: move removing unnecessary packages into base_system role --- roles/base_system/tasks/install_packages.yaml | 43 +++++++ roles/base_system/tasks/main.yaml | 44 +------- roles/base_system/tasks/remove_packages.yaml | 31 ++++++ roles/basic_postinstall_/files/.vimrc | 16 +++ roles/basic_postinstall_/files/.zshrc | 105 ++++++++++++++++++ .../files/hardened_ssh.conf | 4 + .../files/hardened_sshd.conf | 29 +++++ roles/basic_postinstall_/files/install_omz.sh | 15 +++ .../tasks/create_new_user.yaml | 66 +++++++++++ .../basic_postinstall_/tasks/harden_ssh.yaml | 36 ++++++ .../tasks/install_basic_utils.yaml | 42 +++++++ roles/basic_postinstall_/tasks/main.yaml | 18 +++ .../tasks/prepare_ansible_user.yaml | 41 +++++++ .../tasks/remove_packages.yaml | 31 ++++++ .../tasks/set_locale_and_time.yaml | 19 ++++ 15 files changed, 500 insertions(+), 40 deletions(-) create mode 100644 roles/base_system/tasks/install_packages.yaml create mode 100644 roles/base_system/tasks/remove_packages.yaml create mode 100644 roles/basic_postinstall_/files/.vimrc create mode 100644 roles/basic_postinstall_/files/.zshrc create mode 100644 roles/basic_postinstall_/files/hardened_ssh.conf create mode 100644 roles/basic_postinstall_/files/hardened_sshd.conf create mode 100644 roles/basic_postinstall_/files/install_omz.sh create mode 100644 roles/basic_postinstall_/tasks/create_new_user.yaml create mode 100644 roles/basic_postinstall_/tasks/harden_ssh.yaml create mode 100644 roles/basic_postinstall_/tasks/install_basic_utils.yaml create mode 100644 roles/basic_postinstall_/tasks/main.yaml create mode 100644 roles/basic_postinstall_/tasks/prepare_ansible_user.yaml create mode 100644 roles/basic_postinstall_/tasks/remove_packages.yaml create mode 100644 roles/basic_postinstall_/tasks/set_locale_and_time.yaml diff --git a/roles/base_system/tasks/install_packages.yaml b/roles/base_system/tasks/install_packages.yaml new file mode 100644 index 0000000..e462163 --- /dev/null +++ b/roles/base_system/tasks/install_packages.yaml @@ -0,0 +1,43 @@ +--- +- name: Installing basic utils for comfort work (apt-based system) + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + name: + - vim + - neovim + - ranger + - zsh + - rsync + - git + - curl + - kitty + - unattended-upgrades + - ssh + - openssh-server + update-cache: true # Run apt update before installation + become: true + remote_user: ansible + +- name: Install qemu-guest-agent on VM + when: + - ansible_facts['os_family'] == "Debian" + - ansible_facts['virtualization_type'] == "kvm" + ansible.builtin.apt: + name: qemu-guest-agent + state: present + update-cache: true # Run apt update before installation + become: true + remote_user: ansible + tags: + - kvm-guests + - packages + + +# The same commands for Alpine +- name: Update and install packages on Alpine + when: (ansible_facts['distribution'] == "Alpine") + community.general.apk: + name: neovim vim ranger zsh rsync git curl kitty openssh + update_cache: true + remote_user: ansible diff --git a/roles/base_system/tasks/main.yaml b/roles/base_system/tasks/main.yaml index e462163..8f5debe 100644 --- a/roles/base_system/tasks/main.yaml +++ b/roles/base_system/tasks/main.yaml @@ -1,43 +1,7 @@ --- -- name: Installing basic utils for comfort work (apt-based system) - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - name: - - vim - - neovim - - ranger - - zsh - - rsync - - git - - curl - - kitty - - unattended-upgrades - - ssh - - openssh-server - update-cache: true # Run apt update before installation - become: true - remote_user: ansible +- name: Install basic utils + ansible.builtin.include_tasks: install_basic_utils.yaml -- name: Install qemu-guest-agent on VM - when: - - ansible_facts['os_family'] == "Debian" - - ansible_facts['virtualization_type'] == "kvm" - ansible.builtin.apt: - name: qemu-guest-agent - state: present - update-cache: true # Run apt update before installation - become: true - remote_user: ansible - tags: - - kvm-guests - - packages +- name: Remove unnecessary packages + ansible.builtin.include_tasks: remove_packages.yaml - -# The same commands for Alpine -- name: Update and install packages on Alpine - when: (ansible_facts['distribution'] == "Alpine") - community.general.apk: - name: neovim vim ranger zsh rsync git curl kitty openssh - update_cache: true - remote_user: ansible diff --git a/roles/base_system/tasks/remove_packages.yaml b/roles/base_system/tasks/remove_packages.yaml new file mode 100644 index 0000000..c0116fd --- /dev/null +++ b/roles/base_system/tasks/remove_packages.yaml @@ -0,0 +1,31 @@ +--- +# Remove multiple packages at once +- name: Remove unnecessary packages + remote_user: ansible + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + name: + - nano + state: absent + become: true + + +# Clean up all orphaned packages +- name: Remove all orphaned dependencies + remote_user: ansible + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + autoremove: true + purge: true + + +- name: Install sudo package on Alpine + remote_user: ansible + when: (ansible_facts['distribution'] == "Alpine") + community.general.apk: + name: + - nano + state: absent + become: true diff --git a/roles/basic_postinstall_/files/.vimrc b/roles/basic_postinstall_/files/.vimrc new file mode 100644 index 0000000..70d5a79 --- /dev/null +++ b/roles/basic_postinstall_/files/.vimrc @@ -0,0 +1,16 @@ +set number +set tabstop=2 +" Disable compatibility with vi which can cause unexpected issues. +set nocompatible + +" Enable type file detection. Vim will be able to try to detect the type of file in use. +filetype on + +" Enable plugins and load plugin for the detected file type. +filetype plugin on + +" Load an indent file for the detected file type. +filetype indent on + +" Turn syntax highlighting on. +syntax on diff --git a/roles/basic_postinstall_/files/.zshrc b/roles/basic_postinstall_/files/.zshrc new file mode 100644 index 0000000..4423d1f --- /dev/null +++ b/roles/basic_postinstall_/files/.zshrc @@ -0,0 +1,105 @@ +export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:/home/max/soft/gnu_linux:$PATH + +# Path to your Oh My Zsh installation. +export ZSH="$HOME/.oh-my-zsh" + +export GTK_THEME=Adwaita-dark + +# Set name of the theme to load --- if set to "random", it will +# load a random theme each time Oh My Zsh is loaded, in which case, +# to know which specific one was loaded, run: echo $RANDOM_THEME +# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes +ZSH_THEME="gnzh" + +# Set list of themes to pick from when loading at random +# Setting this variable when ZSH_THEME=random will cause zsh to load +# a theme from this variable instead of looking in $ZSH/themes/ +# If set to an empty array, this variable will have no effect. +# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" ) + +# Uncomment the following line to use case-sensitive completion. +# CASE_SENSITIVE="true" + +# Uncomment the following line to use hyphen-insensitive completion. +# Case-sensitive completion must be off. _ and - will be interchangeable. +# HYPHEN_INSENSITIVE="true" + +# Uncomment one of the following lines to change the auto-update behavior +# zstyle ':omz:update' mode disabled # disable automatic updates +# zstyle ':omz:update' mode auto # update automatically without asking +# zstyle ':omz:update' mode reminder # just remind me to update when it's time + +# Uncomment the following line to change how often to auto-update (in days). +# zstyle ':omz:update' frequency 13 + +# Uncomment the following line if pasting URLs and other text is messed up. +# DISABLE_MAGIC_FUNCTIONS="true" + +# Uncomment the following line to disable colors in ls. +# DISABLE_LS_COLORS="true" + +# Uncomment the following line to disable auto-setting terminal title. +# DISABLE_AUTO_TITLE="true" + +# Uncomment the following line to enable command auto-correction. +# ENABLE_CORRECTION="true" + +# Uncomment the following line to display red dots whilst waiting for completion. +# You can also set it to another string to have that shown instead of the default red dots. +# e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f" +# Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765) +# COMPLETION_WAITING_DOTS="true" + +# Uncomment the following line if you want to disable marking untracked files +# under VCS as dirty. This makes repository status check for large repositories +# much, much faster. +# DISABLE_UNTRACKED_FILES_DIRTY="true" + +# Uncomment the following line if you want to change the command execution time +# stamp shown in the history command output. +# You can set one of the optional three formats: +# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" +# or set a custom format using the strftime function format specifications, +# see 'man strftime' for details. +# HIST_STAMPS="mm/dd/yyyy" + +# Would you like to use another custom folder than $ZSH/custom? +# ZSH_CUSTOM=/path/to/new-custom-folder + +# Which plugins would you like to load? +# Standard plugins can be found in $ZSH/plugins/ +# Custom plugins may be added to $ZSH_CUSTOM/plugins/ +# Example format: plugins=(rails git textmate ruby lighthouse) +# Add wisely, as too many plugins slow down shell startup. +#plugins=(git) + +source $ZSH/oh-my-zsh.sh + +# User configuration + +# export MANPATH="/usr/local/man:$MANPATH" + +# You may need to manually set your language environment +# export LANG=en_US.UTF-8 + +# Preferred editor for local and remote sessions +# if [[ -n $SSH_CONNECTION ]]; then +# export EDITOR='vim' +# else +# export EDITOR='nvim' +# fi + +# Compilation flags +# export ARCHFLAGS="-arch $(uname -m)" + +# Set personal aliases, overriding those provided by Oh My Zsh libs, +# plugins, and themes. Aliases can be placed here, though Oh My Zsh +# users are encouraged to define aliases within a top-level file in +# the $ZSH_CUSTOM folder, with .zsh extension. Examples: +# - $ZSH_CUSTOM/aliases.zsh +# - $ZSH_CUSTOM/macos.zsh +# For a full list of active aliases, run `alias`. +# +# Example aliases +# alias zshconfig="mate ~/.zshrc" +# alias ohmyzsh="mate ~/.oh-my-zsh" diff --git a/roles/basic_postinstall_/files/hardened_ssh.conf b/roles/basic_postinstall_/files/hardened_ssh.conf new file mode 100644 index 0000000..012011c --- /dev/null +++ b/roles/basic_postinstall_/files/hardened_ssh.conf @@ -0,0 +1,4 @@ + Host * + HashKnownHosts yes + GSSAPIAuthentication yes + KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,curve25519-sha256 \ No newline at end of file diff --git a/roles/basic_postinstall_/files/hardened_sshd.conf b/roles/basic_postinstall_/files/hardened_sshd.conf new file mode 100644 index 0000000..21f4436 --- /dev/null +++ b/roles/basic_postinstall_/files/hardened_sshd.conf @@ -0,0 +1,29 @@ +PubkeyAuthentication yes +AuthorizedKeysFile .ssh/authorized_keys +PasswordAuthentication no +KbdInteractiveAuthentication no +UsePAM no +# Disable password authentication — keys only +PasswordAuthentication no +ChallengeResponseAuthentication no + + +AllowGroups sshusers +PrintMotd no +AcceptEnv LANG LC_* +ClientAliveCountMax 0 +ClientAliveInterval 300 +Port 22 + + +# Disable root login entirely +PermitRootLogin no + +# Limit authentication attempts +MaxAuthTries 3 +MaxSessions 3 + +# Use modern key exchange and ciphers, prioritize post-quantum algorithms (mlkem and sntrup) +KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org +Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com +MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com \ No newline at end of file diff --git a/roles/basic_postinstall_/files/install_omz.sh b/roles/basic_postinstall_/files/install_omz.sh new file mode 100644 index 0000000..e0e5f8d --- /dev/null +++ b/roles/basic_postinstall_/files/install_omz.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +FILE=/home/$USER/.oh-my-zsh/oh-my-zsh.sh +if [ -f "$FILE" ]; then + echo "$FILE exists and we not installing ohmyzsh" + exit 0 +else + echo "$FILE does not exist and we install ohmyzsh" + cd /home/max + wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh + chmod +x /home/max/install.sh + timeout -s 15 -k 30s 20s /home/max/install.sh --unattendend --keep-zshrc || exit 0 + exit 0 +fi + diff --git a/roles/basic_postinstall_/tasks/create_new_user.yaml b/roles/basic_postinstall_/tasks/create_new_user.yaml new file mode 100644 index 0000000..8ba891b --- /dev/null +++ b/roles/basic_postinstall_/tasks/create_new_user.yaml @@ -0,0 +1,66 @@ +--- +- name: Create a new user with a password, set shell + remote_user: ansible + ansible.builtin.user: + name: max + groups: sshusers,sudo + password: "{{ user_passwd_hash }}" + shell: /bin/zsh + +- name: Set authorized key taken from file + remote_user: ansible + ansible.posix.authorized_key: + user: max + state: present + key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}" + +- name: Copy omz installation wrapper script to the target machine + remote_user: ansible + ansible.builtin.copy: + src: "{{ role_path }}/files/install_omz.sh" + dest: /home/max/install_omz.sh + owner: max + group: max + mode: u=rwx,g=r,o-rwx + +# WARNING: UNPRIVILEGED USER (not ansible) COMMANDS +- name: Install oh my zsh + remote_user: max + become: false + ansible.builtin.command: /home/max/install_omz.sh + changed_when: true + +- name: Configure oh my zsh, by pushing the config file + remote_user: ansible + ansible.builtin.copy: + src: "{{ role_path }}/files/.zshrc" + dest: /home/max/.zshrc + owner: max + group: max + mode: u=rw,g=r,o-rwx + +- name: Configure vim, by pushing the config + remote_user: ansible + ansible.builtin.copy: + src: "{{ role_path }}/files/.vimrc" + dest: /home/max/.vimrc + owner: max + group: max + mode: u=rw,g=r,o-rwx + + +# WARNING: we've finished with the initial setup, drop ansible key +# Push regular user key +- name: Set authorized key taken from file + remote_user: ansible + ansible.posix.authorized_key: + user: max + state: absent + key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}" + +- name: Set authorized key taken from file + remote_user: ansible + ansible.posix.authorized_key: + user: max + state: present + key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/max_regular_key.pub') }}" diff --git a/roles/basic_postinstall_/tasks/harden_ssh.yaml b/roles/basic_postinstall_/tasks/harden_ssh.yaml new file mode 100644 index 0000000..a1486dd --- /dev/null +++ b/roles/basic_postinstall_/tasks/harden_ssh.yaml @@ -0,0 +1,36 @@ +--- + +- name: Configure ssh-server daemon + ansible.builtin.copy: + src: "{{ role_path }}/files/hardened_sshd.conf" + dest: /etc/ssh/sshd_config.d/hardened_sshd.conf + mode: u=rw,g=r,o=r + when: ansible_facts['distribution'] == 'Ubuntu' + +- name: Configure ssh-server daemon + ansible.builtin.copy: + src: "{{ role_path }}/files/hardened_sshd.conf" + dest: /etc/ssh/sshd_config.d/hardened_sshd.conf + mode: u=rw,g=r,o=r + when: ansible_facts['distribution'] == 'Debian' + +- name: Configure ssh client + remote_user: ansible + ansible.builtin.copy: + src: "{{ role_path }}/files/hardened_ssh.conf" + dest: /etc/ssh/ssh_config.d/hardened_ssh.conf + mode: u=rw,g=r,o=r + +- name: Restart ssh-server Debian + remote_user: ansible + ansible.builtin.service: + name: sshd + state: restarted + when: ansible_facts['distribution'] == 'Debian' + +- name: Restart ssh-server Ubuntu + remote_user: ansible + ansible.builtin.service: + name: ssh + state: restarted + when: ansible_facts['distribution'] == 'Ubuntu' diff --git a/roles/basic_postinstall_/tasks/install_basic_utils.yaml b/roles/basic_postinstall_/tasks/install_basic_utils.yaml new file mode 100644 index 0000000..5a6ed89 --- /dev/null +++ b/roles/basic_postinstall_/tasks/install_basic_utils.yaml @@ -0,0 +1,42 @@ +--- +- name: Installing basic utils for comfort work (apt-based system) + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + name: + - vim + - ranger + - zsh + - rsync + - git + - curl + - kitty + - unattended-upgrades + - ssh + - openssh-server + update-cache: true # Run apt update before installation + become: true + remote_user: ansible + +- name: Install qemu-guest-agent on VM + when: + - ansible_facts['os_family'] == "Debian" + - ansible_facts['virtualization_type'] == "kvm" + ansible.builtin.apt: + name: qemu-guest-agent + state: present + update-cache: true # Run apt update before installation + become: true + remote_user: ansible + tags: + - kvm-guests + - packages + + +# The same commands for Alpine +- name: Update and install packages on Alpine + when: (ansible_facts['distribution'] == "Alpine") + community.general.apk: + name: vim ranger zsh rsync git curl kitty openssh + update_cache: true + remote_user: ansible diff --git a/roles/basic_postinstall_/tasks/main.yaml b/roles/basic_postinstall_/tasks/main.yaml new file mode 100644 index 0000000..b886049 --- /dev/null +++ b/roles/basic_postinstall_/tasks/main.yaml @@ -0,0 +1,18 @@ +--- +- name: Create and set up Ansible user and environment + ansible.builtin.include_tasks: prepare_ansible_user.yaml + +- name: Improve SSH configuration + ansible.builtin.include_tasks: harden_ssh.yaml + +- name: Install basic utils + ansible.builtin.include_tasks: install_basic_utils.yaml + +- name: Remove unnecessary packages + ansible.builtin.include_tasks: remove_packages.yaml + +- name: Create and set up a new user + ansible.builtin.include_tasks: create_new_user.yaml + +- name: Set locale and time + ansible.builtin.include_tasks: set_locale_and_time.yaml diff --git a/roles/basic_postinstall_/tasks/prepare_ansible_user.yaml b/roles/basic_postinstall_/tasks/prepare_ansible_user.yaml new file mode 100644 index 0000000..2097ae9 --- /dev/null +++ b/roles/basic_postinstall_/tasks/prepare_ansible_user.yaml @@ -0,0 +1,41 @@ +--- +## Installing packages +- name: Install sudo on apt systems + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + name: + - sudo + update-cache: true + +# The same commands for Alpine +- name: Update and install packages on Alpine + when: (ansible_facts['distribution'] == "Alpine") + community.general.apk: + name: sudo + update_cache: true + remote_user: ansible + + +## Creating and setting up the ansible user +## First, create sshusers group to grant ssh access +- name: Ensure group "sshusers" exists + ansible.builtin.group: + name: sshusers + state: present + +## Add the user to sshusers (for ssh access) and sudo (gain root access) +- name: Create a new user with a password for Ansible + ansible.builtin.user: + name: ansible + password: "{{ ansible_user_passwd_hash }}" + + groups: sshusers,sudo + append: true + +## Since password authentication in SSH will be disabled, we need to add an authorized key +- name: Set authorized key taken from file + ansible.posix.authorized_key: + user: ansible + state: present + key: "{{ ansible_ssh_key }}" diff --git a/roles/basic_postinstall_/tasks/remove_packages.yaml b/roles/basic_postinstall_/tasks/remove_packages.yaml new file mode 100644 index 0000000..c0116fd --- /dev/null +++ b/roles/basic_postinstall_/tasks/remove_packages.yaml @@ -0,0 +1,31 @@ +--- +# Remove multiple packages at once +- name: Remove unnecessary packages + remote_user: ansible + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + name: + - nano + state: absent + become: true + + +# Clean up all orphaned packages +- name: Remove all orphaned dependencies + remote_user: ansible + when: (ansible_facts['distribution'] == "Debian") or + (ansible_facts['distribution'] == "Ubuntu") + ansible.builtin.apt: + autoremove: true + purge: true + + +- name: Install sudo package on Alpine + remote_user: ansible + when: (ansible_facts['distribution'] == "Alpine") + community.general.apk: + name: + - nano + state: absent + become: true diff --git a/roles/basic_postinstall_/tasks/set_locale_and_time.yaml b/roles/basic_postinstall_/tasks/set_locale_and_time.yaml new file mode 100644 index 0000000..37eb4f6 --- /dev/null +++ b/roles/basic_postinstall_/tasks/set_locale_and_time.yaml @@ -0,0 +1,19 @@ +--- +- name: Generate locales + community.general.locale_gen: + name: + - en_US.UTF-8 + - ru_RU.UTF-8 + state: present + +- name: Set locale + ansible.builtin.copy: + dest: /etc/locale.conf + mode: '0644' + content: | + LANG=en_US.UTF-8 + LC_ALL=en_US.UTF-8 + +- name: Set time + community.general.timezone: + name: Europe/Samara -- 2.52.0 From 4e6ffafeef0b115a87dc2caaa78b726b1d27f9c5 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 17:42:15 +0400 Subject: [PATCH 08/37] REFACTOR: move creation of a human admin user into a separated role --- .../files/hardened_ssh.conf | 4 -- .../files/hardened_sshd.conf | 29 ------------- .../basic_postinstall_/tasks/harden_ssh.yaml | 36 ---------------- .../tasks/install_basic_utils.yaml | 42 ------------------- roles/basic_postinstall_/tasks/main.yaml | 18 -------- .../tasks/prepare_ansible_user.yaml | 41 ------------------ .../tasks/remove_packages.yaml | 31 -------------- .../tasks/set_locale_and_time.yaml | 19 --------- .../files/.vimrc | 0 .../files/.zshrc | 0 .../files/install_omz.sh | 0 .../tasks/main.yaml} | 0 12 files changed, 220 deletions(-) delete mode 100644 roles/basic_postinstall_/files/hardened_ssh.conf delete mode 100644 roles/basic_postinstall_/files/hardened_sshd.conf delete mode 100644 roles/basic_postinstall_/tasks/harden_ssh.yaml delete mode 100644 roles/basic_postinstall_/tasks/install_basic_utils.yaml delete mode 100644 roles/basic_postinstall_/tasks/main.yaml delete mode 100644 roles/basic_postinstall_/tasks/prepare_ansible_user.yaml delete mode 100644 roles/basic_postinstall_/tasks/remove_packages.yaml delete mode 100644 roles/basic_postinstall_/tasks/set_locale_and_time.yaml rename roles/{basic_postinstall_ => human_admin_user}/files/.vimrc (100%) rename roles/{basic_postinstall_ => human_admin_user}/files/.zshrc (100%) rename roles/{basic_postinstall_ => human_admin_user}/files/install_omz.sh (100%) rename roles/{basic_postinstall_/tasks/create_new_user.yaml => human_admin_user/tasks/main.yaml} (100%) diff --git a/roles/basic_postinstall_/files/hardened_ssh.conf b/roles/basic_postinstall_/files/hardened_ssh.conf deleted file mode 100644 index 012011c..0000000 --- a/roles/basic_postinstall_/files/hardened_ssh.conf +++ /dev/null @@ -1,4 +0,0 @@ - Host * - HashKnownHosts yes - GSSAPIAuthentication yes - KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,curve25519-sha256 \ No newline at end of file diff --git a/roles/basic_postinstall_/files/hardened_sshd.conf b/roles/basic_postinstall_/files/hardened_sshd.conf deleted file mode 100644 index 21f4436..0000000 --- a/roles/basic_postinstall_/files/hardened_sshd.conf +++ /dev/null @@ -1,29 +0,0 @@ -PubkeyAuthentication yes -AuthorizedKeysFile .ssh/authorized_keys -PasswordAuthentication no -KbdInteractiveAuthentication no -UsePAM no -# Disable password authentication — keys only -PasswordAuthentication no -ChallengeResponseAuthentication no - - -AllowGroups sshusers -PrintMotd no -AcceptEnv LANG LC_* -ClientAliveCountMax 0 -ClientAliveInterval 300 -Port 22 - - -# Disable root login entirely -PermitRootLogin no - -# Limit authentication attempts -MaxAuthTries 3 -MaxSessions 3 - -# Use modern key exchange and ciphers, prioritize post-quantum algorithms (mlkem and sntrup) -KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org -Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com -MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com \ No newline at end of file diff --git a/roles/basic_postinstall_/tasks/harden_ssh.yaml b/roles/basic_postinstall_/tasks/harden_ssh.yaml deleted file mode 100644 index a1486dd..0000000 --- a/roles/basic_postinstall_/tasks/harden_ssh.yaml +++ /dev/null @@ -1,36 +0,0 @@ ---- - -- name: Configure ssh-server daemon - ansible.builtin.copy: - src: "{{ role_path }}/files/hardened_sshd.conf" - dest: /etc/ssh/sshd_config.d/hardened_sshd.conf - mode: u=rw,g=r,o=r - when: ansible_facts['distribution'] == 'Ubuntu' - -- name: Configure ssh-server daemon - ansible.builtin.copy: - src: "{{ role_path }}/files/hardened_sshd.conf" - dest: /etc/ssh/sshd_config.d/hardened_sshd.conf - mode: u=rw,g=r,o=r - when: ansible_facts['distribution'] == 'Debian' - -- name: Configure ssh client - remote_user: ansible - ansible.builtin.copy: - src: "{{ role_path }}/files/hardened_ssh.conf" - dest: /etc/ssh/ssh_config.d/hardened_ssh.conf - mode: u=rw,g=r,o=r - -- name: Restart ssh-server Debian - remote_user: ansible - ansible.builtin.service: - name: sshd - state: restarted - when: ansible_facts['distribution'] == 'Debian' - -- name: Restart ssh-server Ubuntu - remote_user: ansible - ansible.builtin.service: - name: ssh - state: restarted - when: ansible_facts['distribution'] == 'Ubuntu' diff --git a/roles/basic_postinstall_/tasks/install_basic_utils.yaml b/roles/basic_postinstall_/tasks/install_basic_utils.yaml deleted file mode 100644 index 5a6ed89..0000000 --- a/roles/basic_postinstall_/tasks/install_basic_utils.yaml +++ /dev/null @@ -1,42 +0,0 @@ ---- -- name: Installing basic utils for comfort work (apt-based system) - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - name: - - vim - - ranger - - zsh - - rsync - - git - - curl - - kitty - - unattended-upgrades - - ssh - - openssh-server - update-cache: true # Run apt update before installation - become: true - remote_user: ansible - -- name: Install qemu-guest-agent on VM - when: - - ansible_facts['os_family'] == "Debian" - - ansible_facts['virtualization_type'] == "kvm" - ansible.builtin.apt: - name: qemu-guest-agent - state: present - update-cache: true # Run apt update before installation - become: true - remote_user: ansible - tags: - - kvm-guests - - packages - - -# The same commands for Alpine -- name: Update and install packages on Alpine - when: (ansible_facts['distribution'] == "Alpine") - community.general.apk: - name: vim ranger zsh rsync git curl kitty openssh - update_cache: true - remote_user: ansible diff --git a/roles/basic_postinstall_/tasks/main.yaml b/roles/basic_postinstall_/tasks/main.yaml deleted file mode 100644 index b886049..0000000 --- a/roles/basic_postinstall_/tasks/main.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -- name: Create and set up Ansible user and environment - ansible.builtin.include_tasks: prepare_ansible_user.yaml - -- name: Improve SSH configuration - ansible.builtin.include_tasks: harden_ssh.yaml - -- name: Install basic utils - ansible.builtin.include_tasks: install_basic_utils.yaml - -- name: Remove unnecessary packages - ansible.builtin.include_tasks: remove_packages.yaml - -- name: Create and set up a new user - ansible.builtin.include_tasks: create_new_user.yaml - -- name: Set locale and time - ansible.builtin.include_tasks: set_locale_and_time.yaml diff --git a/roles/basic_postinstall_/tasks/prepare_ansible_user.yaml b/roles/basic_postinstall_/tasks/prepare_ansible_user.yaml deleted file mode 100644 index 2097ae9..0000000 --- a/roles/basic_postinstall_/tasks/prepare_ansible_user.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -## Installing packages -- name: Install sudo on apt systems - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - name: - - sudo - update-cache: true - -# The same commands for Alpine -- name: Update and install packages on Alpine - when: (ansible_facts['distribution'] == "Alpine") - community.general.apk: - name: sudo - update_cache: true - remote_user: ansible - - -## Creating and setting up the ansible user -## First, create sshusers group to grant ssh access -- name: Ensure group "sshusers" exists - ansible.builtin.group: - name: sshusers - state: present - -## Add the user to sshusers (for ssh access) and sudo (gain root access) -- name: Create a new user with a password for Ansible - ansible.builtin.user: - name: ansible - password: "{{ ansible_user_passwd_hash }}" - - groups: sshusers,sudo - append: true - -## Since password authentication in SSH will be disabled, we need to add an authorized key -- name: Set authorized key taken from file - ansible.posix.authorized_key: - user: ansible - state: present - key: "{{ ansible_ssh_key }}" diff --git a/roles/basic_postinstall_/tasks/remove_packages.yaml b/roles/basic_postinstall_/tasks/remove_packages.yaml deleted file mode 100644 index c0116fd..0000000 --- a/roles/basic_postinstall_/tasks/remove_packages.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- -# Remove multiple packages at once -- name: Remove unnecessary packages - remote_user: ansible - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - name: - - nano - state: absent - become: true - - -# Clean up all orphaned packages -- name: Remove all orphaned dependencies - remote_user: ansible - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - autoremove: true - purge: true - - -- name: Install sudo package on Alpine - remote_user: ansible - when: (ansible_facts['distribution'] == "Alpine") - community.general.apk: - name: - - nano - state: absent - become: true diff --git a/roles/basic_postinstall_/tasks/set_locale_and_time.yaml b/roles/basic_postinstall_/tasks/set_locale_and_time.yaml deleted file mode 100644 index 37eb4f6..0000000 --- a/roles/basic_postinstall_/tasks/set_locale_and_time.yaml +++ /dev/null @@ -1,19 +0,0 @@ ---- -- name: Generate locales - community.general.locale_gen: - name: - - en_US.UTF-8 - - ru_RU.UTF-8 - state: present - -- name: Set locale - ansible.builtin.copy: - dest: /etc/locale.conf - mode: '0644' - content: | - LANG=en_US.UTF-8 - LC_ALL=en_US.UTF-8 - -- name: Set time - community.general.timezone: - name: Europe/Samara diff --git a/roles/basic_postinstall_/files/.vimrc b/roles/human_admin_user/files/.vimrc similarity index 100% rename from roles/basic_postinstall_/files/.vimrc rename to roles/human_admin_user/files/.vimrc diff --git a/roles/basic_postinstall_/files/.zshrc b/roles/human_admin_user/files/.zshrc similarity index 100% rename from roles/basic_postinstall_/files/.zshrc rename to roles/human_admin_user/files/.zshrc diff --git a/roles/basic_postinstall_/files/install_omz.sh b/roles/human_admin_user/files/install_omz.sh similarity index 100% rename from roles/basic_postinstall_/files/install_omz.sh rename to roles/human_admin_user/files/install_omz.sh diff --git a/roles/basic_postinstall_/tasks/create_new_user.yaml b/roles/human_admin_user/tasks/main.yaml similarity index 100% rename from roles/basic_postinstall_/tasks/create_new_user.yaml rename to roles/human_admin_user/tasks/main.yaml -- 2.52.0 From cb70968aaf014540d680692cfd0c08b0c6d77976 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 17:43:47 +0400 Subject: [PATCH 09/37] REFACTOR: move setting locale and time into a separated role --- .../tasks/set_locale_and_time.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 roles/set_locale_and_time/tasks/set_locale_and_time.yaml diff --git a/roles/set_locale_and_time/tasks/set_locale_and_time.yaml b/roles/set_locale_and_time/tasks/set_locale_and_time.yaml new file mode 100644 index 0000000..37eb4f6 --- /dev/null +++ b/roles/set_locale_and_time/tasks/set_locale_and_time.yaml @@ -0,0 +1,19 @@ +--- +- name: Generate locales + community.general.locale_gen: + name: + - en_US.UTF-8 + - ru_RU.UTF-8 + state: present + +- name: Set locale + ansible.builtin.copy: + dest: /etc/locale.conf + mode: '0644' + content: | + LANG=en_US.UTF-8 + LC_ALL=en_US.UTF-8 + +- name: Set time + community.general.timezone: + name: Europe/Samara -- 2.52.0 From fe8255352596c6c5067488d0fb55c3487066cacb Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 18:20:27 +0400 Subject: [PATCH 10/37] REFACTOR: remove unnecessary files. Add to deploy_and_set_up_lxc_on_proxmox configuration steps --- .../deploy_and_set_up_lxc_on_proxmox.yaml | 36 +++++- roles/basic_postinstall/files/.vimrc | 16 --- roles/basic_postinstall/files/.zshrc | 105 ------------------ .../basic_postinstall/files/hardened_ssh.conf | 4 - .../files/hardened_sshd.conf | 29 ----- roles/basic_postinstall/files/install_omz.sh | 15 --- .../tasks/create_new_user.yaml | 66 ----------- roles/basic_postinstall/tasks/harden_ssh.yaml | 36 ------ .../tasks/install_basic_utils.yaml | 42 ------- roles/basic_postinstall/tasks/main.yaml | 18 --- .../tasks/prepare_ansible_user.yaml | 41 ------- .../tasks/remove_packages.yaml | 31 ------ .../tasks/set_locale_and_time.yaml | 19 ---- 13 files changed, 35 insertions(+), 423 deletions(-) delete mode 100644 roles/basic_postinstall/files/.vimrc delete mode 100644 roles/basic_postinstall/files/.zshrc delete mode 100644 roles/basic_postinstall/files/hardened_ssh.conf delete mode 100644 roles/basic_postinstall/files/hardened_sshd.conf delete mode 100644 roles/basic_postinstall/files/install_omz.sh delete mode 100644 roles/basic_postinstall/tasks/create_new_user.yaml delete mode 100644 roles/basic_postinstall/tasks/harden_ssh.yaml delete mode 100644 roles/basic_postinstall/tasks/install_basic_utils.yaml delete mode 100644 roles/basic_postinstall/tasks/main.yaml delete mode 100644 roles/basic_postinstall/tasks/prepare_ansible_user.yaml delete mode 100644 roles/basic_postinstall/tasks/remove_packages.yaml delete mode 100644 roles/basic_postinstall/tasks/set_locale_and_time.yaml diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml index 2530724..1bbfb32 100644 --- a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -1,5 +1,5 @@ --- -- name: Physical machines maintanance play +- name: Deploy LXC hosts: all remote_user: ansible gather_facts: false @@ -28,3 +28,37 @@ roles: - ../roles/deploy_lxc_on_proxmox + + tasks: + - name: Make the prompted hostname available to the whole playbook + ansible.builtin.set_fact: + fact_lxc_hostname: "{{ lxc_hostname }}" + + - name: Make the prompted IP available to the whole playbook + ansible.builtin.set_fact: + fact_lxc_ip_address: "{{ lxc_ip_address }}" + + - name: Add the target host to the inventory + ansible.builtin.add_host: + name: "{{ fact_lxc_ip_address }}" + groups: new_host + ansible_user: ansible + + +- name: Configure LXC + hosts: new_host + remote_user: ansible + + vars_files: + ../inventory/group_vars/all/secrets.yaml + vars: + ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" + user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" + + roles: + - ../roles/configure_ansible_user + - ../roles/harden_ssh + - ../roles/base_system + - ../roles/human_admin_user + - ../roles/set_locale_and_time + diff --git a/roles/basic_postinstall/files/.vimrc b/roles/basic_postinstall/files/.vimrc deleted file mode 100644 index 70d5a79..0000000 --- a/roles/basic_postinstall/files/.vimrc +++ /dev/null @@ -1,16 +0,0 @@ -set number -set tabstop=2 -" Disable compatibility with vi which can cause unexpected issues. -set nocompatible - -" Enable type file detection. Vim will be able to try to detect the type of file in use. -filetype on - -" Enable plugins and load plugin for the detected file type. -filetype plugin on - -" Load an indent file for the detected file type. -filetype indent on - -" Turn syntax highlighting on. -syntax on diff --git a/roles/basic_postinstall/files/.zshrc b/roles/basic_postinstall/files/.zshrc deleted file mode 100644 index 4423d1f..0000000 --- a/roles/basic_postinstall/files/.zshrc +++ /dev/null @@ -1,105 +0,0 @@ -export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:/home/max/soft/gnu_linux:$PATH - -# Path to your Oh My Zsh installation. -export ZSH="$HOME/.oh-my-zsh" - -export GTK_THEME=Adwaita-dark - -# Set name of the theme to load --- if set to "random", it will -# load a random theme each time Oh My Zsh is loaded, in which case, -# to know which specific one was loaded, run: echo $RANDOM_THEME -# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes -ZSH_THEME="gnzh" - -# Set list of themes to pick from when loading at random -# Setting this variable when ZSH_THEME=random will cause zsh to load -# a theme from this variable instead of looking in $ZSH/themes/ -# If set to an empty array, this variable will have no effect. -# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" ) - -# Uncomment the following line to use case-sensitive completion. -# CASE_SENSITIVE="true" - -# Uncomment the following line to use hyphen-insensitive completion. -# Case-sensitive completion must be off. _ and - will be interchangeable. -# HYPHEN_INSENSITIVE="true" - -# Uncomment one of the following lines to change the auto-update behavior -# zstyle ':omz:update' mode disabled # disable automatic updates -# zstyle ':omz:update' mode auto # update automatically without asking -# zstyle ':omz:update' mode reminder # just remind me to update when it's time - -# Uncomment the following line to change how often to auto-update (in days). -# zstyle ':omz:update' frequency 13 - -# Uncomment the following line if pasting URLs and other text is messed up. -# DISABLE_MAGIC_FUNCTIONS="true" - -# Uncomment the following line to disable colors in ls. -# DISABLE_LS_COLORS="true" - -# Uncomment the following line to disable auto-setting terminal title. -# DISABLE_AUTO_TITLE="true" - -# Uncomment the following line to enable command auto-correction. -# ENABLE_CORRECTION="true" - -# Uncomment the following line to display red dots whilst waiting for completion. -# You can also set it to another string to have that shown instead of the default red dots. -# e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f" -# Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765) -# COMPLETION_WAITING_DOTS="true" - -# Uncomment the following line if you want to disable marking untracked files -# under VCS as dirty. This makes repository status check for large repositories -# much, much faster. -# DISABLE_UNTRACKED_FILES_DIRTY="true" - -# Uncomment the following line if you want to change the command execution time -# stamp shown in the history command output. -# You can set one of the optional three formats: -# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" -# or set a custom format using the strftime function format specifications, -# see 'man strftime' for details. -# HIST_STAMPS="mm/dd/yyyy" - -# Would you like to use another custom folder than $ZSH/custom? -# ZSH_CUSTOM=/path/to/new-custom-folder - -# Which plugins would you like to load? -# Standard plugins can be found in $ZSH/plugins/ -# Custom plugins may be added to $ZSH_CUSTOM/plugins/ -# Example format: plugins=(rails git textmate ruby lighthouse) -# Add wisely, as too many plugins slow down shell startup. -#plugins=(git) - -source $ZSH/oh-my-zsh.sh - -# User configuration - -# export MANPATH="/usr/local/man:$MANPATH" - -# You may need to manually set your language environment -# export LANG=en_US.UTF-8 - -# Preferred editor for local and remote sessions -# if [[ -n $SSH_CONNECTION ]]; then -# export EDITOR='vim' -# else -# export EDITOR='nvim' -# fi - -# Compilation flags -# export ARCHFLAGS="-arch $(uname -m)" - -# Set personal aliases, overriding those provided by Oh My Zsh libs, -# plugins, and themes. Aliases can be placed here, though Oh My Zsh -# users are encouraged to define aliases within a top-level file in -# the $ZSH_CUSTOM folder, with .zsh extension. Examples: -# - $ZSH_CUSTOM/aliases.zsh -# - $ZSH_CUSTOM/macos.zsh -# For a full list of active aliases, run `alias`. -# -# Example aliases -# alias zshconfig="mate ~/.zshrc" -# alias ohmyzsh="mate ~/.oh-my-zsh" diff --git a/roles/basic_postinstall/files/hardened_ssh.conf b/roles/basic_postinstall/files/hardened_ssh.conf deleted file mode 100644 index 012011c..0000000 --- a/roles/basic_postinstall/files/hardened_ssh.conf +++ /dev/null @@ -1,4 +0,0 @@ - Host * - HashKnownHosts yes - GSSAPIAuthentication yes - KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,curve25519-sha256 \ No newline at end of file diff --git a/roles/basic_postinstall/files/hardened_sshd.conf b/roles/basic_postinstall/files/hardened_sshd.conf deleted file mode 100644 index 21f4436..0000000 --- a/roles/basic_postinstall/files/hardened_sshd.conf +++ /dev/null @@ -1,29 +0,0 @@ -PubkeyAuthentication yes -AuthorizedKeysFile .ssh/authorized_keys -PasswordAuthentication no -KbdInteractiveAuthentication no -UsePAM no -# Disable password authentication — keys only -PasswordAuthentication no -ChallengeResponseAuthentication no - - -AllowGroups sshusers -PrintMotd no -AcceptEnv LANG LC_* -ClientAliveCountMax 0 -ClientAliveInterval 300 -Port 22 - - -# Disable root login entirely -PermitRootLogin no - -# Limit authentication attempts -MaxAuthTries 3 -MaxSessions 3 - -# Use modern key exchange and ciphers, prioritize post-quantum algorithms (mlkem and sntrup) -KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org -Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com -MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com \ No newline at end of file diff --git a/roles/basic_postinstall/files/install_omz.sh b/roles/basic_postinstall/files/install_omz.sh deleted file mode 100644 index e0e5f8d..0000000 --- a/roles/basic_postinstall/files/install_omz.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -FILE=/home/$USER/.oh-my-zsh/oh-my-zsh.sh -if [ -f "$FILE" ]; then - echo "$FILE exists and we not installing ohmyzsh" - exit 0 -else - echo "$FILE does not exist and we install ohmyzsh" - cd /home/max - wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh - chmod +x /home/max/install.sh - timeout -s 15 -k 30s 20s /home/max/install.sh --unattendend --keep-zshrc || exit 0 - exit 0 -fi - diff --git a/roles/basic_postinstall/tasks/create_new_user.yaml b/roles/basic_postinstall/tasks/create_new_user.yaml deleted file mode 100644 index 8ba891b..0000000 --- a/roles/basic_postinstall/tasks/create_new_user.yaml +++ /dev/null @@ -1,66 +0,0 @@ ---- -- name: Create a new user with a password, set shell - remote_user: ansible - ansible.builtin.user: - name: max - groups: sshusers,sudo - password: "{{ user_passwd_hash }}" - shell: /bin/zsh - -- name: Set authorized key taken from file - remote_user: ansible - ansible.posix.authorized_key: - user: max - state: present - key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}" - -- name: Copy omz installation wrapper script to the target machine - remote_user: ansible - ansible.builtin.copy: - src: "{{ role_path }}/files/install_omz.sh" - dest: /home/max/install_omz.sh - owner: max - group: max - mode: u=rwx,g=r,o-rwx - -# WARNING: UNPRIVILEGED USER (not ansible) COMMANDS -- name: Install oh my zsh - remote_user: max - become: false - ansible.builtin.command: /home/max/install_omz.sh - changed_when: true - -- name: Configure oh my zsh, by pushing the config file - remote_user: ansible - ansible.builtin.copy: - src: "{{ role_path }}/files/.zshrc" - dest: /home/max/.zshrc - owner: max - group: max - mode: u=rw,g=r,o-rwx - -- name: Configure vim, by pushing the config - remote_user: ansible - ansible.builtin.copy: - src: "{{ role_path }}/files/.vimrc" - dest: /home/max/.vimrc - owner: max - group: max - mode: u=rw,g=r,o-rwx - - -# WARNING: we've finished with the initial setup, drop ansible key -# Push regular user key -- name: Set authorized key taken from file - remote_user: ansible - ansible.posix.authorized_key: - user: max - state: absent - key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}" - -- name: Set authorized key taken from file - remote_user: ansible - ansible.posix.authorized_key: - user: max - state: present - key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/max_regular_key.pub') }}" diff --git a/roles/basic_postinstall/tasks/harden_ssh.yaml b/roles/basic_postinstall/tasks/harden_ssh.yaml deleted file mode 100644 index a1486dd..0000000 --- a/roles/basic_postinstall/tasks/harden_ssh.yaml +++ /dev/null @@ -1,36 +0,0 @@ ---- - -- name: Configure ssh-server daemon - ansible.builtin.copy: - src: "{{ role_path }}/files/hardened_sshd.conf" - dest: /etc/ssh/sshd_config.d/hardened_sshd.conf - mode: u=rw,g=r,o=r - when: ansible_facts['distribution'] == 'Ubuntu' - -- name: Configure ssh-server daemon - ansible.builtin.copy: - src: "{{ role_path }}/files/hardened_sshd.conf" - dest: /etc/ssh/sshd_config.d/hardened_sshd.conf - mode: u=rw,g=r,o=r - when: ansible_facts['distribution'] == 'Debian' - -- name: Configure ssh client - remote_user: ansible - ansible.builtin.copy: - src: "{{ role_path }}/files/hardened_ssh.conf" - dest: /etc/ssh/ssh_config.d/hardened_ssh.conf - mode: u=rw,g=r,o=r - -- name: Restart ssh-server Debian - remote_user: ansible - ansible.builtin.service: - name: sshd - state: restarted - when: ansible_facts['distribution'] == 'Debian' - -- name: Restart ssh-server Ubuntu - remote_user: ansible - ansible.builtin.service: - name: ssh - state: restarted - when: ansible_facts['distribution'] == 'Ubuntu' diff --git a/roles/basic_postinstall/tasks/install_basic_utils.yaml b/roles/basic_postinstall/tasks/install_basic_utils.yaml deleted file mode 100644 index 5a6ed89..0000000 --- a/roles/basic_postinstall/tasks/install_basic_utils.yaml +++ /dev/null @@ -1,42 +0,0 @@ ---- -- name: Installing basic utils for comfort work (apt-based system) - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - name: - - vim - - ranger - - zsh - - rsync - - git - - curl - - kitty - - unattended-upgrades - - ssh - - openssh-server - update-cache: true # Run apt update before installation - become: true - remote_user: ansible - -- name: Install qemu-guest-agent on VM - when: - - ansible_facts['os_family'] == "Debian" - - ansible_facts['virtualization_type'] == "kvm" - ansible.builtin.apt: - name: qemu-guest-agent - state: present - update-cache: true # Run apt update before installation - become: true - remote_user: ansible - tags: - - kvm-guests - - packages - - -# The same commands for Alpine -- name: Update and install packages on Alpine - when: (ansible_facts['distribution'] == "Alpine") - community.general.apk: - name: vim ranger zsh rsync git curl kitty openssh - update_cache: true - remote_user: ansible diff --git a/roles/basic_postinstall/tasks/main.yaml b/roles/basic_postinstall/tasks/main.yaml deleted file mode 100644 index b886049..0000000 --- a/roles/basic_postinstall/tasks/main.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -- name: Create and set up Ansible user and environment - ansible.builtin.include_tasks: prepare_ansible_user.yaml - -- name: Improve SSH configuration - ansible.builtin.include_tasks: harden_ssh.yaml - -- name: Install basic utils - ansible.builtin.include_tasks: install_basic_utils.yaml - -- name: Remove unnecessary packages - ansible.builtin.include_tasks: remove_packages.yaml - -- name: Create and set up a new user - ansible.builtin.include_tasks: create_new_user.yaml - -- name: Set locale and time - ansible.builtin.include_tasks: set_locale_and_time.yaml diff --git a/roles/basic_postinstall/tasks/prepare_ansible_user.yaml b/roles/basic_postinstall/tasks/prepare_ansible_user.yaml deleted file mode 100644 index 2097ae9..0000000 --- a/roles/basic_postinstall/tasks/prepare_ansible_user.yaml +++ /dev/null @@ -1,41 +0,0 @@ ---- -## Installing packages -- name: Install sudo on apt systems - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - name: - - sudo - update-cache: true - -# The same commands for Alpine -- name: Update and install packages on Alpine - when: (ansible_facts['distribution'] == "Alpine") - community.general.apk: - name: sudo - update_cache: true - remote_user: ansible - - -## Creating and setting up the ansible user -## First, create sshusers group to grant ssh access -- name: Ensure group "sshusers" exists - ansible.builtin.group: - name: sshusers - state: present - -## Add the user to sshusers (for ssh access) and sudo (gain root access) -- name: Create a new user with a password for Ansible - ansible.builtin.user: - name: ansible - password: "{{ ansible_user_passwd_hash }}" - - groups: sshusers,sudo - append: true - -## Since password authentication in SSH will be disabled, we need to add an authorized key -- name: Set authorized key taken from file - ansible.posix.authorized_key: - user: ansible - state: present - key: "{{ ansible_ssh_key }}" diff --git a/roles/basic_postinstall/tasks/remove_packages.yaml b/roles/basic_postinstall/tasks/remove_packages.yaml deleted file mode 100644 index c0116fd..0000000 --- a/roles/basic_postinstall/tasks/remove_packages.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- -# Remove multiple packages at once -- name: Remove unnecessary packages - remote_user: ansible - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - name: - - nano - state: absent - become: true - - -# Clean up all orphaned packages -- name: Remove all orphaned dependencies - remote_user: ansible - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") - ansible.builtin.apt: - autoremove: true - purge: true - - -- name: Install sudo package on Alpine - remote_user: ansible - when: (ansible_facts['distribution'] == "Alpine") - community.general.apk: - name: - - nano - state: absent - become: true diff --git a/roles/basic_postinstall/tasks/set_locale_and_time.yaml b/roles/basic_postinstall/tasks/set_locale_and_time.yaml deleted file mode 100644 index 37eb4f6..0000000 --- a/roles/basic_postinstall/tasks/set_locale_and_time.yaml +++ /dev/null @@ -1,19 +0,0 @@ ---- -- name: Generate locales - community.general.locale_gen: - name: - - en_US.UTF-8 - - ru_RU.UTF-8 - state: present - -- name: Set locale - ansible.builtin.copy: - dest: /etc/locale.conf - mode: '0644' - content: | - LANG=en_US.UTF-8 - LC_ALL=en_US.UTF-8 - -- name: Set time - community.general.timezone: - name: Europe/Samara -- 2.52.0 From 82fcbf51275dfeab054c0fe3aab8a7f7c685617a Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 9 Aug 2026 18:29:52 +0400 Subject: [PATCH 11/37] REFACTOR: remove run_playbook.sh, it's useless --- run_playbook.sh | 4 ---- 1 file changed, 4 deletions(-) delete mode 100755 run_playbook.sh diff --git a/run_playbook.sh b/run_playbook.sh deleted file mode 100755 index 8fd8521..0000000 --- a/run_playbook.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -ansible-playbook -i inventory/hosts.yaml $1 \ - --private-key inventory/group_vars/secret/ansible_key \ - --vault-password-file inventory/group_vars/secret/.vault_pass \ No newline at end of file -- 2.52.0 From 14c0b05bd2a9b3e87edb129195b6e0e0ab77f0e8 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Tue, 11 Aug 2026 08:42:51 +0400 Subject: [PATCH 12/37] REFACTOR: deploy_and_set_up_lxc_on_proxmox.yaml works again --- playbooks/deploy_and_set_up_lxc_on_proxmox.yaml | 4 +++- roles/base_system/tasks/main.yaml | 2 +- roles/human_admin_user/tasks/main.yaml | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml index 1bbfb32..75a6bf3 100644 --- a/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml +++ b/playbooks/deploy_and_set_up_lxc_on_proxmox.yaml @@ -36,13 +36,15 @@ - name: Make the prompted IP available to the whole playbook ansible.builtin.set_fact: - fact_lxc_ip_address: "{{ lxc_ip_address }}" + #fact_lxc_ip_address: "{{ lxc_ip_address | ansible.utils.ipaddr('address') }}" + fact_lxc_ip_address: "{{ lxc_ip_address.split('/') | first }}" - name: Add the target host to the inventory ansible.builtin.add_host: name: "{{ fact_lxc_ip_address }}" groups: new_host ansible_user: ansible + ansible_become_password: "{{ ansible_become_passwd }}" - name: Configure LXC diff --git a/roles/base_system/tasks/main.yaml b/roles/base_system/tasks/main.yaml index 8f5debe..459ff7d 100644 --- a/roles/base_system/tasks/main.yaml +++ b/roles/base_system/tasks/main.yaml @@ -1,6 +1,6 @@ --- - name: Install basic utils - ansible.builtin.include_tasks: install_basic_utils.yaml + ansible.builtin.include_tasks: install_packages.yaml - name: Remove unnecessary packages ansible.builtin.include_tasks: remove_packages.yaml diff --git a/roles/human_admin_user/tasks/main.yaml b/roles/human_admin_user/tasks/main.yaml index 8ba891b..19b5b29 100644 --- a/roles/human_admin_user/tasks/main.yaml +++ b/roles/human_admin_user/tasks/main.yaml @@ -24,11 +24,13 @@ mode: u=rwx,g=r,o-rwx # WARNING: UNPRIVILEGED USER (not ansible) COMMANDS +# Since the golden image contains installed zsh, this step may not be necessary - name: Install oh my zsh remote_user: max become: false ansible.builtin.command: /home/max/install_omz.sh changed_when: true + failed_when: false - name: Configure oh my zsh, by pushing the config file remote_user: ansible -- 2.52.0 From ff948437c88d1e1865108a96e408185e5d5327a2 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Tue, 11 Aug 2026 09:57:15 +0400 Subject: [PATCH 13/37] REFACTOR: replace distribution with os_family --- roles/base_system/tasks/install_packages.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/roles/base_system/tasks/install_packages.yaml b/roles/base_system/tasks/install_packages.yaml index e462163..cef556c 100644 --- a/roles/base_system/tasks/install_packages.yaml +++ b/roles/base_system/tasks/install_packages.yaml @@ -1,7 +1,6 @@ --- - name: Installing basic utils for comfort work (apt-based system) - when: (ansible_facts['distribution'] == "Debian") or - (ansible_facts['distribution'] == "Ubuntu") + when: (ansible_facts['os_family'] == "Debian") ansible.builtin.apt: name: - vim -- 2.52.0 From afca9912cf7fd29b90a8959d59b5fcef1cf0c254 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Tue, 11 Aug 2026 13:03:08 +0400 Subject: [PATCH 14/37] REFACTOR: add maintenance roles doing configs stuff and system updates, add dependencies for neovim in base system --- playbooks/maintain.yaml | 13 +- roles/base_system/tasks/install_packages.yaml | 3 + roles/common_healthcheck/tasks/main.yaml | 7 +- roles/update_configs/files/.vimrc | 16 + roles/update_configs/files/.zshrc | 105 + .../update_configs/files/nvim_config/init.lua | 2 + .../files/nvim_config/lazy-lock.json | 24 + .../files/nvim_config/lua/config/lazy.lua | 35 + .../files/nvim_config/lua/options.lua | 25 + .../nvim_config/lua/plugins/autosave.lua | 15 + .../nvim_config/lua/plugins/colorscheme.lua | 78 + .../nvim_config/lua/plugins/treesitter.lua | 62 + .../files/ranger_config/commands.py | 62 + .../files/ranger_config/commands_full.py | 1993 +++++++++++++++++ .../files/ranger_config/rc.conf | 759 +++++++ .../files/ranger_config/rifle.conf | 272 +++ .../files/ranger_config/scope.sh | 349 +++ roles/update_configs/tasks/main.yaml | 32 + roles/update_system/tasks/main.yaml | 19 + 19 files changed, 3866 insertions(+), 5 deletions(-) create mode 100644 roles/update_configs/files/.vimrc create mode 100644 roles/update_configs/files/.zshrc create mode 100644 roles/update_configs/files/nvim_config/init.lua create mode 100644 roles/update_configs/files/nvim_config/lazy-lock.json create mode 100644 roles/update_configs/files/nvim_config/lua/config/lazy.lua create mode 100644 roles/update_configs/files/nvim_config/lua/options.lua create mode 100644 roles/update_configs/files/nvim_config/lua/plugins/autosave.lua create mode 100644 roles/update_configs/files/nvim_config/lua/plugins/colorscheme.lua create mode 100644 roles/update_configs/files/nvim_config/lua/plugins/treesitter.lua create mode 100644 roles/update_configs/files/ranger_config/commands.py create mode 100644 roles/update_configs/files/ranger_config/commands_full.py create mode 100644 roles/update_configs/files/ranger_config/rc.conf create mode 100644 roles/update_configs/files/ranger_config/rifle.conf create mode 100755 roles/update_configs/files/ranger_config/scope.sh create mode 100644 roles/update_configs/tasks/main.yaml create mode 100644 roles/update_system/tasks/main.yaml diff --git a/playbooks/maintain.yaml b/playbooks/maintain.yaml index bd8d31a..1642953 100644 --- a/playbooks/maintain.yaml +++ b/playbooks/maintain.yaml @@ -2,10 +2,19 @@ - name: Deploy and set up an LXC container in Proxmox hosts: all remote_user: ansible - roles: - - ../roles/deploy_lxc_on_proxmox + vars_files: ../inventory/group_vars/all/secrets.yaml vars: ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}" user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}" + + roles: + # Check the Internet connection + # Check free disk space + ../roles/common_healthcheck + # Update the system + ../roles/update_system + # Harden SSH - we must be sure that the last version of ssh configs are distributed + ../roles/harden_ssh + # Update configs - omz, nvim, ranger and so on. Distribute the last version of those configs diff --git a/roles/base_system/tasks/install_packages.yaml b/roles/base_system/tasks/install_packages.yaml index cef556c..ae8a736 100644 --- a/roles/base_system/tasks/install_packages.yaml +++ b/roles/base_system/tasks/install_packages.yaml @@ -5,6 +5,9 @@ name: - vim - neovim + - gcc + - tree-sitter-cli + - luarocks - ranger - zsh - rsync diff --git a/roles/common_healthcheck/tasks/main.yaml b/roles/common_healthcheck/tasks/main.yaml index 6a08e66..c024da8 100644 --- a/roles/common_healthcheck/tasks/main.yaml +++ b/roles/common_healthcheck/tasks/main.yaml @@ -2,7 +2,7 @@ - name: Internet connection test block block: - name: Test reachability to ya.ru - become: true # Usually it's not necessary, but sometimes there are some wierd issues with ping, especially on Alpine + become: true # Usually it's not necessary, but sometimes ping is disabled for non-root by default ansible.builtin.shell: ping -c 5 ya.ru > /dev/null changed_when: false # This task does not change the system @@ -21,9 +21,10 @@ block: - name: Test free disk space in root become: false + # We have to use this complicated pipeline because of Alpine and its wierd df implementation ansible.builtin.shell: set -o pipefail && df -h / | tail -1 | awk '{gsub(/%/, "", $5); print $5}' - register: common_healthcheck_result - failed_when: common_healthcheck_result.stdout | int > 85 + register: free_disk_space_result + failed_when: free_disk_space_result.stdout | int > 85 changed_when: false # This task does not change the system rescue: diff --git a/roles/update_configs/files/.vimrc b/roles/update_configs/files/.vimrc new file mode 100644 index 0000000..70d5a79 --- /dev/null +++ b/roles/update_configs/files/.vimrc @@ -0,0 +1,16 @@ +set number +set tabstop=2 +" Disable compatibility with vi which can cause unexpected issues. +set nocompatible + +" Enable type file detection. Vim will be able to try to detect the type of file in use. +filetype on + +" Enable plugins and load plugin for the detected file type. +filetype plugin on + +" Load an indent file for the detected file type. +filetype indent on + +" Turn syntax highlighting on. +syntax on diff --git a/roles/update_configs/files/.zshrc b/roles/update_configs/files/.zshrc new file mode 100644 index 0000000..4423d1f --- /dev/null +++ b/roles/update_configs/files/.zshrc @@ -0,0 +1,105 @@ +export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:/home/max/soft/gnu_linux:$PATH + +# Path to your Oh My Zsh installation. +export ZSH="$HOME/.oh-my-zsh" + +export GTK_THEME=Adwaita-dark + +# Set name of the theme to load --- if set to "random", it will +# load a random theme each time Oh My Zsh is loaded, in which case, +# to know which specific one was loaded, run: echo $RANDOM_THEME +# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes +ZSH_THEME="gnzh" + +# Set list of themes to pick from when loading at random +# Setting this variable when ZSH_THEME=random will cause zsh to load +# a theme from this variable instead of looking in $ZSH/themes/ +# If set to an empty array, this variable will have no effect. +# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" ) + +# Uncomment the following line to use case-sensitive completion. +# CASE_SENSITIVE="true" + +# Uncomment the following line to use hyphen-insensitive completion. +# Case-sensitive completion must be off. _ and - will be interchangeable. +# HYPHEN_INSENSITIVE="true" + +# Uncomment one of the following lines to change the auto-update behavior +# zstyle ':omz:update' mode disabled # disable automatic updates +# zstyle ':omz:update' mode auto # update automatically without asking +# zstyle ':omz:update' mode reminder # just remind me to update when it's time + +# Uncomment the following line to change how often to auto-update (in days). +# zstyle ':omz:update' frequency 13 + +# Uncomment the following line if pasting URLs and other text is messed up. +# DISABLE_MAGIC_FUNCTIONS="true" + +# Uncomment the following line to disable colors in ls. +# DISABLE_LS_COLORS="true" + +# Uncomment the following line to disable auto-setting terminal title. +# DISABLE_AUTO_TITLE="true" + +# Uncomment the following line to enable command auto-correction. +# ENABLE_CORRECTION="true" + +# Uncomment the following line to display red dots whilst waiting for completion. +# You can also set it to another string to have that shown instead of the default red dots. +# e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f" +# Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765) +# COMPLETION_WAITING_DOTS="true" + +# Uncomment the following line if you want to disable marking untracked files +# under VCS as dirty. This makes repository status check for large repositories +# much, much faster. +# DISABLE_UNTRACKED_FILES_DIRTY="true" + +# Uncomment the following line if you want to change the command execution time +# stamp shown in the history command output. +# You can set one of the optional three formats: +# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" +# or set a custom format using the strftime function format specifications, +# see 'man strftime' for details. +# HIST_STAMPS="mm/dd/yyyy" + +# Would you like to use another custom folder than $ZSH/custom? +# ZSH_CUSTOM=/path/to/new-custom-folder + +# Which plugins would you like to load? +# Standard plugins can be found in $ZSH/plugins/ +# Custom plugins may be added to $ZSH_CUSTOM/plugins/ +# Example format: plugins=(rails git textmate ruby lighthouse) +# Add wisely, as too many plugins slow down shell startup. +#plugins=(git) + +source $ZSH/oh-my-zsh.sh + +# User configuration + +# export MANPATH="/usr/local/man:$MANPATH" + +# You may need to manually set your language environment +# export LANG=en_US.UTF-8 + +# Preferred editor for local and remote sessions +# if [[ -n $SSH_CONNECTION ]]; then +# export EDITOR='vim' +# else +# export EDITOR='nvim' +# fi + +# Compilation flags +# export ARCHFLAGS="-arch $(uname -m)" + +# Set personal aliases, overriding those provided by Oh My Zsh libs, +# plugins, and themes. Aliases can be placed here, though Oh My Zsh +# users are encouraged to define aliases within a top-level file in +# the $ZSH_CUSTOM folder, with .zsh extension. Examples: +# - $ZSH_CUSTOM/aliases.zsh +# - $ZSH_CUSTOM/macos.zsh +# For a full list of active aliases, run `alias`. +# +# Example aliases +# alias zshconfig="mate ~/.zshrc" +# alias ohmyzsh="mate ~/.oh-my-zsh" diff --git a/roles/update_configs/files/nvim_config/init.lua b/roles/update_configs/files/nvim_config/init.lua new file mode 100644 index 0000000..77c10eb --- /dev/null +++ b/roles/update_configs/files/nvim_config/init.lua @@ -0,0 +1,2 @@ +require("config.lazy") +require("options") diff --git a/roles/update_configs/files/nvim_config/lazy-lock.json b/roles/update_configs/files/nvim_config/lazy-lock.json new file mode 100644 index 0000000..a7378c8 --- /dev/null +++ b/roles/update_configs/files/nvim_config/lazy-lock.json @@ -0,0 +1,24 @@ +{ + "auto-save.nvim": { "branch": "main", "commit": "9aabcb8396224dcbf8d51c0c1d620d88a46e89d7" }, + "bamboo.nvim": { "branch": "master", "commit": "1309bc88bffcf1bedc3e84e7fa9004de93da774a" }, + "cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" }, + "dial.nvim": { "branch": "master", "commit": "f2634758455cfa52a8acea6f142dcd6271a1bf57" }, + "dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" }, + "hererocks": { "branch": "master", "commit": "204ab1ff8b68cb7db7c9bafb4be2abf7d22f864e" }, + "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, + "lua-utils.nvim": { "branch": "main", "commit": "e565749421f4bbb5d2e85e37c3cef9d56553d8bd" }, + "neorg": { "branch": "main", "commit": "d4dd8979c1129b5251d5565164f54d1cc258e92a" }, + "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, + "nvim-cmp": { "branch": "main", "commit": "2ffe79f1f021def8dd1fcd81deb16f1bb0d989f3" }, + "nvim-nio": { "branch": "master", "commit": "edcc181a875301dd21840189aa2f2f9ad69fc172" }, + "nvim-treesitter": { "branch": "main", "commit": "c9f9ed6c1892f629ea399f4ee7905f2686fa13f2" }, + "nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" }, + "nvim-web-devicons": { "branch": "master", "commit": "2ae6958df7ced50baac5035cec0c15799eedfbf7" }, + "pathlib.nvim": { "branch": "main", "commit": "57e5598af6fe253761c1b48e0b59b7cd6699e2c1" }, + "tree-sitter-norg": { "branch": "main", "commit": "d7edfaf89198aab652c7a1f0f818196efedaccfb" }, + "tree-sitter-norg-meta": { "branch": "main", "commit": "729d4e54fb881ba0ddf0f925ec78401354c7c6db" }, + "treesj": { "branch": "main", "commit": "79aedb401bbdc7e4202f7881eab5f6feb2105b0a" }, + "vim-startuptime": { "branch": "master", "commit": "5f33e50f1e2e2a80370c9094e4c303ea54cd2aea" }, + "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } +} diff --git a/roles/update_configs/files/nvim_config/lua/config/lazy.lua b/roles/update_configs/files/nvim_config/lua/config/lazy.lua new file mode 100644 index 0000000..f5ee74c --- /dev/null +++ b/roles/update_configs/files/nvim_config/lua/config/lazy.lua @@ -0,0 +1,35 @@ +-- Bootstrap lazy.nvim +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not (vim.uv or vim.loop).fs_stat(lazypath) then + local lazyrepo = "https://github.com/folke/lazy.nvim.git" + local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) + if vim.v.shell_error ~= 0 then + vim.api.nvim_echo({ + { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, + { out, "WarningMsg" }, + { "\nPress any key to exit..." }, + }, true, {}) + vim.fn.getchar() + os.exit(1) + end +end +vim.opt.rtp:prepend(lazypath) + +-- Make sure to setup `mapleader` and `maplocalleader` before +-- loading lazy.nvim so that mappings are correct. +-- This is also a good place to setup other settings (vim.opt) +vim.g.mapleader = " " +vim.g.maplocalleader = "\\" + +-- Setup lazy.nvim +require("lazy").setup({ + spec = { + -- import your plugins + { import = "plugins" }, + }, + -- Configure any other settings here. See the documentation for more details. + -- colorscheme that will be used when installing plugins. + install = { colorscheme = { "habamax" } }, + -- automatically check for plugin updates + checker = { enabled = true }, +}) diff --git a/roles/update_configs/files/nvim_config/lua/options.lua b/roles/update_configs/files/nvim_config/lua/options.lua new file mode 100644 index 0000000..fa75fb6 --- /dev/null +++ b/roles/update_configs/files/nvim_config/lua/options.lua @@ -0,0 +1,25 @@ +vim.opt.clipboard = 'unnamedplus' -- use system clipboard +vim.opt.completeopt = {'menu', 'menuone', 'noselect'} + +-- Tab +vim.opt.tabstop = 2 -- number of visual spaces per TAB +vim.opt.softtabstop = 2 -- number of spacesin tab when editing +vim.opt.shiftwidth = 2 -- insert 2 spaces on a tab +vim.opt.expandtab = true -- tabs are spaces, mainly because of python + +-- UI config +vim.opt.number = true -- show absolute number +vim.opt.cursorline = true -- highlight cursor line underneath the cursor horizontally +vim.opt.splitbelow = true -- open new vertical split bottom +vim.opt.splitright = true -- open new horizontal splits right +vim.opt.termguicolors = true -- enable 24-bit RGB color in the TUI + +-- Searching +vim.opt.incsearch = true -- search as characters are entered +vim.opt.hlsearch = true -- do (not) highlight matches +vim.opt.ignorecase = true -- ignore case in searches by default +vim.opt.smartcase = true -- but make it case sensitive if an uppercase is entered + +-- Code folding +vim.opt.foldmethod = "indent" + diff --git a/roles/update_configs/files/nvim_config/lua/plugins/autosave.lua b/roles/update_configs/files/nvim_config/lua/plugins/autosave.lua new file mode 100644 index 0000000..cc8172d --- /dev/null +++ b/roles/update_configs/files/nvim_config/lua/plugins/autosave.lua @@ -0,0 +1,15 @@ +return { + "okuuva/auto-save.nvim", + cmd = "ASToggle", -- optional for lazy loading on command + event = { "InsertLeave", "TextChanged" }, -- optional for lazy loading on trigger events + opts = { + -- your config goes here + -- or just leave it empty :) + }, + keys = { + { "sa", ":ASToggle", desc = "Toggle auto-save" }, + }, + config = function() + require("auto-save").setup { enabled = true } + end, +} diff --git a/roles/update_configs/files/nvim_config/lua/plugins/colorscheme.lua b/roles/update_configs/files/nvim_config/lua/plugins/colorscheme.lua new file mode 100644 index 0000000..62183d5 --- /dev/null +++ b/roles/update_configs/files/nvim_config/lua/plugins/colorscheme.lua @@ -0,0 +1,78 @@ +return { + -- the colorscheme should be available when starting Neovim + { + 'ribru17/bamboo.nvim', + lazy = false, + priority = 1000, + config = function() + require('bamboo').setup { + -- optional configuration here + } + require('bamboo').load() + end, + }, + + -- I have a separate config.mappings file where I require which-key. + -- With lazy the plugin will be automatically loaded when it is required somewhere + { "folke/which-key.nvim", lazy = true }, + + { + "nvim-neorg/neorg", + -- lazy-load on filetype + ft = "norg", + -- options for neorg. This will automatically call `require("neorg").setup(opts)` + opts = { + load = { + ["core.defaults"] = {}, + }, + }, + }, + + { + "dstein64/vim-startuptime", + -- lazy-load on a command + cmd = "StartupTime", + -- init is called during startup. Configuration for vim plugins typically should be set in an init function + init = function() + vim.g.startuptime_tries = 10 + end, + }, + + { + "hrsh7th/nvim-cmp", + -- load cmp on InsertEnter + event = "InsertEnter", + -- these dependencies will only be loaded when cmp loads + -- dependencies are always lazy-loaded unless specified otherwise + dependencies = { + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + }, + config = function() + -- ... + end, + }, + + -- if some code requires a module from an unloaded plugin, it will be automatically loaded. + -- So for api plugins like devicons, we can always set lazy=true + { "nvim-tree/nvim-web-devicons", lazy = true }, + + -- you can use the VeryLazy event for things that can + -- load later and are not important for the initial UI + { "stevearc/dressing.nvim", event = "VeryLazy" }, + + { + "Wansmer/treesj", + keys = { + { "J", "TSJToggle", desc = "Join Toggle" }, + }, + opts = { use_default_keymaps = false, max_join_length = 150 }, + }, + + { + "monaqa/dial.nvim", + -- lazy-load on keys + -- mode is `n` by default. For more advanced options, check the section on key mappings + keys = { "", { "", mode = "n" } }, + }, +} diff --git a/roles/update_configs/files/nvim_config/lua/plugins/treesitter.lua b/roles/update_configs/files/nvim_config/lua/plugins/treesitter.lua new file mode 100644 index 0000000..679caca --- /dev/null +++ b/roles/update_configs/files/nvim_config/lua/plugins/treesitter.lua @@ -0,0 +1,62 @@ +--- ~/nvim/lua/slydragonn/plugins/treesiter.lua + +return { + "nvim-treesitter/nvim-treesitter", + event = { "BufReadPre", "BufNewFile" }, + build = ":TSUpdate", + dependencies = { + "windwp/nvim-ts-autotag", + }, + config = function() + local treesitter = require("nvim-treesitter.config") + + treesitter.setup({ + highlight = { + enable = true, + additional_vim_regex_highlighting = false, + }, + indent = { enable = true }, + autotag = { + enable = true, + }, + ensure_installed = { + "json", + "javascript", + "typescript", + "tsx", + "yaml", + "html", + "css", + "markdown", + "markdown_inline", + "bash", + "lua", + "vim", + "dockerfile", + "gitignore", + "c", + "rust", + }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = "", + node_incremental = "", + scope_incremental = false, + node_decremental = "", + }, + }, + rainbow = { + enable = true, + disable = { "html" }, + extended_mode = false, + max_file_lines = nil, + }, + context_commentstring = { + enable = true, + enable_autocmd = false, + }, + }) + end, +} + diff --git a/roles/update_configs/files/ranger_config/commands.py b/roles/update_configs/files/ranger_config/commands.py new file mode 100644 index 0000000..97b7909 --- /dev/null +++ b/roles/update_configs/files/ranger_config/commands.py @@ -0,0 +1,62 @@ +# This is a sample commands.py. You can add your own commands here. +# +# Please refer to commands_full.py for all the default commands and a complete +# documentation. Do NOT add them all here, or you may end up with defunct +# commands when upgrading ranger. + +# A simple command for demonstration purposes follows. +# ----------------------------------------------------------------------------- + +from __future__ import (absolute_import, division, print_function) + +# You can import any python module as needed. +import os + +# You always need to import ranger.api.commands here to get the Command class: +from ranger.api.commands import Command + + +# Any class that is a subclass of "Command" will be integrated into ranger as a +# command. Try typing ":my_edit" in ranger! +class my_edit(Command): + # The so-called doc-string of the class will be visible in the built-in + # help that is accessible by typing "?c" inside ranger. + """:my_edit + + A sample command for demonstration purposes that opens a file in an editor. + """ + + # The execute method is called when you run this command in ranger. + def execute(self): + # self.arg(1) is the first (space-separated) argument to the function. + # This way you can write ":my_edit somefilename". + if self.arg(1): + # self.rest(1) contains self.arg(1) and everything that follows + target_filename = self.rest(1) + else: + # self.fm is a ranger.core.filemanager.FileManager object and gives + # you access to internals of ranger. + # self.fm.thisfile is a ranger.container.file.File object and is a + # reference to the currently selected file. + target_filename = self.fm.thisfile.path + + # This is a generic function to print text in ranger. + self.fm.notify("Let's edit the file " + target_filename + "!") + + # Using bad=True in fm.notify allows you to print error messages: + if not os.path.exists(target_filename): + self.fm.notify("The given file does not exist!", bad=True) + return + + # This executes a function from ranger.core.acitons, a module with a + # variety of subroutines that can help you construct commands. + # Check out the source, or run "pydoc ranger.core.actions" for a list. + self.fm.edit_file(target_filename) + + # The tab method is called when you press tab, and should return a list of + # suggestions that the user will tab through. + # tabnum is 1 for and -1 for by default + def tab(self, tabnum): + # This is a generic tab-completion function that iterates through the + # content of the current directory. + return self._tab_directory_content() diff --git a/roles/update_configs/files/ranger_config/commands_full.py b/roles/update_configs/files/ranger_config/commands_full.py new file mode 100644 index 0000000..5defa67 --- /dev/null +++ b/roles/update_configs/files/ranger_config/commands_full.py @@ -0,0 +1,1993 @@ +# -*- coding: utf-8 -*- +# This file is part of ranger, the console file manager. +# This configuration file is licensed under the same terms as ranger. +# =================================================================== +# +# NOTE: If you copied this file to /etc/ranger/commands_full.py or +# ~/.config/ranger/commands_full.py, then it will NOT be loaded by ranger, +# and only serve as a reference. +# +# =================================================================== +# This file contains ranger's commands. +# It's all in python; lines beginning with # are comments. +# +# Note that additional commands are automatically generated from the methods +# of the class ranger.core.actions.Actions. +# +# You can customize commands in the files /etc/ranger/commands.py (system-wide) +# and ~/.config/ranger/commands.py (per user). +# They have the same syntax as this file. In fact, you can just copy this +# file to ~/.config/ranger/commands_full.py with +# `ranger --copy-config=commands_full' and make your modifications, don't +# forget to rename it to commands.py. You can also use +# `ranger --copy-config=commands' to copy a short sample commands.py that +# has everything you need to get started. +# But make sure you update your configs when you update ranger. +# +# =================================================================== +# Every class defined here which is a subclass of `Command' will be used as a +# command in ranger. Several methods are defined to interface with ranger: +# execute(): called when the command is executed. +# cancel(): called when closing the console. +# tab(tabnum): called when is pressed. +# quick(): called after each keypress. +# +# tab() argument tabnum is 1 for and -1 for by default +# +# The return values for tab() can be either: +# None: There is no tab completion +# A string: Change the console to this string +# A list/tuple/generator: cycle through every item in it +# +# The return value for quick() can be: +# False: Nothing happens +# True: Execute the command afterwards +# +# The return value for execute() and cancel() doesn't matter. +# +# =================================================================== +# Commands have certain attributes and methods that facilitate parsing of +# the arguments: +# +# self.line: The whole line that was written in the console. +# self.args: A list of all (space-separated) arguments to the command. +# self.quantifier: If this command was mapped to the key "X" and +# the user pressed 6X, self.quantifier will be 6. +# self.arg(n): The n-th argument, or an empty string if it doesn't exist. +# self.rest(n): The n-th argument plus everything that followed. For example, +# if the command was "search foo bar a b c", rest(2) will be "bar a b c" +# self.start(n): Anything before the n-th argument. For example, if the +# command was "search foo bar a b c", start(2) will be "search foo" +# +# =================================================================== +# And this is a little reference for common ranger functions and objects: +# +# self.fm: A reference to the "fm" object which contains most information +# about ranger. +# self.fm.notify(string): Print the given string on the screen. +# self.fm.notify(string, bad=True): Print the given string in RED. +# self.fm.reload_cwd(): Reload the current working directory. +# self.fm.thisdir: The current working directory. (A File object.) +# self.fm.thisfile: The current file. (A File object too.) +# self.fm.thistab.get_selection(): A list of all selected files. +# self.fm.execute_console(string): Execute the string as a ranger command. +# self.fm.open_console(string): Open the console with the given string +# already typed in for you. +# self.fm.move(direction): Moves the cursor in the given direction, which +# can be something like down=3, up=5, right=1, left=1, to=6, ... +# +# File objects (for example self.fm.thisfile) have these useful attributes and +# methods: +# +# tfile.path: The path to the file. +# tfile.basename: The base name only. +# tfile.load_content(): Force a loading of the directories content (which +# obviously works with directories only) +# tfile.is_directory: True/False depending on whether it's a directory. +# +# For advanced commands it is unavoidable to dive a bit into the source code +# of ranger. +# =================================================================== + +from __future__ import (absolute_import, division, print_function) + +from collections import deque +import os +import re + +from ranger.api.commands import Command + + +class alias(Command): + """:alias + + Copies the oldcommand as newcommand. + """ + + context = 'browser' + resolve_macros = False + + def execute(self): + if not self.arg(1) or not self.arg(2): + self.fm.notify('Syntax: alias ', bad=True) + return + + self.fm.commands.alias(self.arg(1), self.rest(2)) + + +class echo(Command): + """:echo + + Display the text in the statusbar. + """ + + def execute(self): + self.fm.notify(self.rest(1)) + + +class cd(Command): + """:cd [-r] + + The cd command changes the directory. + If the path is a file, selects that file. + The command 'cd -' is equivalent to typing ``. + Using the option "-r" will get you to the real path. + """ + + def execute(self): + if self.arg(1) == '-r': + self.shift() + destination = os.path.realpath(self.rest(1)) + if os.path.isfile(destination): + self.fm.select_file(destination) + return + else: + destination = self.rest(1) + + if not destination: + destination = '~' + + if destination == '-': + self.fm.enter_bookmark('`') + else: + self.fm.cd(destination) + + def _tab_args(self): + # dest must be rest because path could contain spaces + if self.arg(1) == '-r': + start = self.start(2) + dest = self.rest(2) + else: + start = self.start(1) + dest = self.rest(1) + + if dest: + head, tail = os.path.split(os.path.expanduser(dest)) + if head: + dest_exp = os.path.join(os.path.normpath(head), tail) + else: + dest_exp = tail + else: + dest_exp = '' + return (start, dest_exp, os.path.join(self.fm.thisdir.path, dest_exp), + dest.endswith(os.path.sep)) + + @staticmethod + def _tab_paths(dest, dest_abs, ends_with_sep): + if not dest: + try: + return next(os.walk(dest_abs))[1], dest_abs + except (OSError, StopIteration): + return [], '' + + if ends_with_sep: + try: + return [os.path.join(dest, path) for path in next(os.walk(dest_abs))[1]], '' + except (OSError, StopIteration): + return [], '' + + return None, None + + def _tab_match(self, path_user, path_file): + if self.fm.settings.cd_tab_case == 'insensitive': + path_user = path_user.lower() + path_file = path_file.lower() + elif self.fm.settings.cd_tab_case == 'smart' and path_user.islower(): + path_file = path_file.lower() + return path_file.startswith(path_user) + + def _tab_normal(self, dest, dest_abs): + dest_dir = os.path.dirname(dest) + dest_base = os.path.basename(dest) + + try: + dirnames = next(os.walk(os.path.dirname(dest_abs)))[1] + except (OSError, StopIteration): + return [], '' + + return [os.path.join(dest_dir, d) for d in dirnames if self._tab_match(dest_base, d)], '' + + def _tab_fuzzy_match(self, basepath, tokens): + """ Find directories matching tokens recursively """ + if not tokens: + tokens = [''] + paths = [basepath] + while True: + token = tokens.pop() + matches = [] + for path in paths: + try: + directories = next(os.walk(path))[1] + except (OSError, StopIteration): + continue + matches += [os.path.join(path, d) for d in directories + if self._tab_match(token, d)] + if not tokens or not matches: + return matches + paths = matches + + return None + + def _tab_fuzzy(self, dest, dest_abs): + tokens = [] + basepath = dest_abs + while True: + basepath_old = basepath + basepath, token = os.path.split(basepath) + if basepath == basepath_old: + break + if os.path.isdir(basepath_old) and not token.startswith('.'): + basepath = basepath_old + break + tokens.append(token) + + paths = self._tab_fuzzy_match(basepath, tokens) + if not os.path.isabs(dest): + paths_rel = self.fm.thisdir.path + paths = [os.path.relpath(os.path.join(basepath, path), paths_rel) + for path in paths] + else: + paths_rel = '' + return paths, paths_rel + + def tab(self, tabnum): + from os.path import sep + + start, dest, dest_abs, ends_with_sep = self._tab_args() + + paths, paths_rel = self._tab_paths(dest, dest_abs, ends_with_sep) + if paths is None: + if self.fm.settings.cd_tab_fuzzy: + paths, paths_rel = self._tab_fuzzy(dest, dest_abs) + else: + paths, paths_rel = self._tab_normal(dest, dest_abs) + + paths.sort() + + if self.fm.settings.cd_bookmarks: + paths[0:0] = [ + os.path.relpath(v.path, paths_rel) if paths_rel else v.path + for v in self.fm.bookmarks.dct.values() for path in paths + if v.path.startswith(os.path.join(paths_rel, path) + sep) + ] + + if not paths: + return None + if len(paths) == 1: + return start + paths[0] + sep + return [start + dirname + sep for dirname in paths] + + +class chain(Command): + """:chain ; ; ... + + Calls multiple commands at once, separated by semicolons. + """ + resolve_macros = False + + def execute(self): + if not self.rest(1).strip(): + self.fm.notify('Syntax: chain ; ; ...', bad=True) + return + for command in [s.strip() for s in self.rest(1).split(";")]: + self.fm.execute_console(command) + + +class shell(Command): + escape_macros_for_shell = True + + def execute(self): + if self.arg(1) and self.arg(1)[0] == '-': + flags = self.arg(1)[1:] + command = self.rest(2) + else: + flags = '' + command = self.rest(1) + + if command: + self.fm.execute_command(command, flags=flags) + + def tab(self, tabnum): + from ranger.ext.get_executables import get_executables + if self.arg(1) and self.arg(1)[0] == '-': + command = self.rest(2) + else: + command = self.rest(1) + start = self.line[0:len(self.line) - len(command)] + + try: + position_of_last_space = command.rindex(" ") + except ValueError: + return (start + program + ' ' for program + in get_executables() if program.startswith(command)) + if position_of_last_space == len(command) - 1: + selection = self.fm.thistab.get_selection() + if len(selection) == 1: + return self.line + selection[0].shell_escaped_basename + ' ' + return self.line + '%s ' + + before_word, start_of_word = self.line.rsplit(' ', 1) + return (before_word + ' ' + file.shell_escaped_basename + for file in self.fm.thisdir.files or [] + if file.shell_escaped_basename.startswith(start_of_word)) + + +class open_with(Command): + + def execute(self): + app, flags, mode = self._get_app_flags_mode(self.rest(1)) + self.fm.execute_file( + files=[f for f in self.fm.thistab.get_selection()], + app=app, + flags=flags, + mode=mode) + + def tab(self, tabnum): + return self._tab_through_executables() + + def _get_app_flags_mode(self, string): # pylint: disable=too-many-branches,too-many-statements + """Extracts the application, flags and mode from a string. + + examples: + "mplayer f 1" => ("mplayer", "f", 1) + "atool 4" => ("atool", "", 4) + "p" => ("", "p", 0) + "" => None + """ + + app = '' + flags = '' + mode = 0 + split = string.split() + + if len(split) == 1: + part = split[0] + if self._is_app(part): + app = part + elif self._is_flags(part): + flags = part + elif self._is_mode(part): + mode = part + + elif len(split) == 2: + part0 = split[0] + part1 = split[1] + + if self._is_app(part0): + app = part0 + if self._is_flags(part1): + flags = part1 + elif self._is_mode(part1): + mode = part1 + elif self._is_flags(part0): + flags = part0 + if self._is_mode(part1): + mode = part1 + elif self._is_mode(part0): + mode = part0 + if self._is_flags(part1): + flags = part1 + + elif len(split) >= 3: + part0 = split[0] + part1 = split[1] + part2 = split[2] + + if self._is_app(part0): + app = part0 + if self._is_flags(part1): + flags = part1 + if self._is_mode(part2): + mode = part2 + elif self._is_mode(part1): + mode = part1 + if self._is_flags(part2): + flags = part2 + elif self._is_flags(part0): + flags = part0 + if self._is_mode(part1): + mode = part1 + elif self._is_mode(part0): + mode = part0 + if self._is_flags(part1): + flags = part1 + + return app, flags, int(mode) + + def _is_app(self, arg): + return not self._is_flags(arg) and not arg.isdigit() + + @staticmethod + def _is_flags(arg): + from ranger.core.runner import ALLOWED_FLAGS + return all(x in ALLOWED_FLAGS for x in arg) + + @staticmethod + def _is_mode(arg): + return all(x in '0123456789' for x in arg) + + +class set_(Command): + """:set