Skip to content

Instantly share code, notes, and snippets.

@AlexAtkinson
Last active December 7, 2025 07:56
Show Gist options
  • Select an option

  • Save AlexAtkinson/27b12f4dfda31b1b74fcab3fc9a6d192 to your computer and use it in GitHub Desktop.

Select an option

Save AlexAtkinson/27b12f4dfda31b1b74fcab3fc9a6d192 to your computer and use it in GitHub Desktop.
PC Setup

PC Setup

Codified PC Setup.

Usage

source <(curl -s https://gist.githubusercontent.com/AlexAtkinson/27b12f4dfda31b1b74fcab3fc9a6d192/raw/init.sh)
[defaults]
timeout = 60
inventory = ~/.ansible/hosts
remote_tmp = ~/.ansible/tmp
forks = 150
transport = smart
gathering = smart
roles_path = ~/.ansible/roles
host_key_checking = False
log_path = /var/log/ansible.log
module_name = shell
fact_caching = jsonfile
fact_caching_connection = $HOME/.ansible/facts
fact_caching_timeout = 600
retry_files_save_path = ~/.ansible/retry
[ssh_connection]
pipelining = True
scp_if_ssh = True
[accelerate]
accelerate_port = 5099
accelerate_timeout = 30
accelerate_connect_timeout = 5.0
accelerate_daemon_timeout = 30
- name: "Apt: Safe upgrade"
ansible.builtin.apt:
upgrade: safe
update_cache: true
cache_valid_time: 600
- name: "Apt: Install Packages"
ansible.builtin.apt:
pkg:
- util-linux
- vim
- shellcheck
- openssl
- python3
- python3-pip
- python3-pytest
- curl
- wget
- whois
- netcat-openbsd
- tar
- gzip
- unzip
- bat
- bc
- yq
- jq
- less
- findutils
- ca-certificates
- apt-transport-https
- software-properties-common
- g++
- gccgo
- pkg-config
- make
- cmake
- check
- valgrind
- build-essential
- libbz2-dev
- libcurl4-openssl-dev
- libjson-c-dev
- libmilter-dev
- libncurses5-dev
- libpcre2-dev
- libxml2-dev
- libasound2-dev
- mplayer
- vlc
- multitail
- mise
- nordvpn-gui
- wipe
- guake
- tmux
- btop
- cursor
- ansible
- ansible-lint
- dconf-cli
- xclip
- name: "Apt: Download Mise Key"
ansible.builtin.get_url:
url: https://mise.jdx.dev/gpg-key.pub
dest: /tmp/mise-gpg-key.pub
mode: '0644'
when: "'/etc/apt/trusted.gpg.d/mise.gpg' is not exists"
- name: "Apt: Dearmor Mise Key"
ansible.builtin.shell: gpg --batch --dearmor -o /etc/apt/trusted.gpg.d/mise.gpg /tmp/mise-gpg-key.pub
args:
creates: /etc/apt/trusted.gpg.d/mise.gpg
when: "'/etc/apt/trusted.gpg.d/mise.gpg' is not exists"
- name: "Apt: Add Mise repository"
ansible.builtin.apt_repository:
repo: deb [signed-by=/etc/apt/trusted.gpg.d/mise.gpg arch=amd64] https://mise.jdx.dev/deb stable main
state: present
filename: mise
- name: "NordVPN: Download Key"
ansible.builtin.get_url:
url: https://repo.nordvpn.com/gpg/nordvpn_public.asc
dest: /etc/apt/trusted.gpg.d/nordvpn_public.asc
mode: '0644'
when: "'/etc/apt/trusted.gpg.d/nordvpn_public.asc' is not exists"
- name: "NordVPN: Add Repository"
ansible.builtin.apt_repository:
repo: deb https://repo.nordvpn.com/deb/nordvpn/debian stable main
state: present
filename: nordvpn-app
- name: "Opera: Check installed version"
ansible.builtin.shell: opera --version | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p'
ignore_errors: true
changed_when: false
register: installed_opera_version
- name: "Opera: Report version"
ansible.builtin.debug:
msg: "Installed: {{ installed_opera_version.stdout }}"
- name: "Opera: Download"
ansible.builtin.get_url:
url: https://download5.operacdn.com/ftp/pub/opera/desktop/{{ opera_version }}/linux/opera-stable_{{ opera_version }}_amd64.deb
dest: /tmp/opera-{{ opera_version }}_amd64.deb
mode: '0644'
when: opera_version != installed_opera_version.stdout
- name: "Opera: Install"
ansible.builtin.apt:
deb: /tmp/opera-{{ opera_version }}_amd64.deb
when: opera_version != installed_opera_version.stdout
- name: "VS Code: Check Latest Version"
shell: |
curl -sSkIL "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64" | \
grep Content-Disposition | \
cut -d'_' -f2 | \
cut -d'-' -f1
ignore_errors: yes
changed_when: false
register: vscode_latest_version
- name: "VS Code: Check Installed Version"
shell: |
/usr/bin/code --version | head -n1
ignore_errors: yes
changed_when: false
register: vscode_installed_version
- name: "VS Code: Report Versions"
debug:
msg: "Installed: {{ vscode_installed_version.stdout }}, Latest: {{ vscode_latest_version.stdout }}"
- name: "VS Code: Check Latest Version"
shell: |
curl -sSkIL "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64" | \
grep Content-Disposition | \
cut -d'_' -f2 | \
cut -d'-' -f1
ignore_errors: yes
changed_when: false
register: vscode_latest_version
- name: "VS Code: Check Installed Version"
shell: |
/usr/bin/code --no-sandbox --user-data-dir="$HOME/.vscode" --version | head -n1
ignore_errors: yes
changed_when: false
register: vscode_installed_version
- name: "VS Code: Report Versions"
debug:
msg: "Installed: {{ vscode_installed_version.stdout }}, Latest: {{ vscode_latest_version.stdout }}"
- name: "VS Code: Download"
ansible.builtin.get_url:
url: https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64
dest: temp_assets_dir.path
mode: '0644'
register: vscode_deb_download
when: vscode_installed_version.stdout != vscode_latest_version.stdout
- name: "VS Code: Install"
ansible.builtin.apt:
deb: vscode_deb_download.dest
when: vscode_installed_version.stdout != vscode_latest_version.stdout
# Bash # TODO: Re-enable once $- bug is resolved in 5.3
# - name: BASH - Check installed version
# shell: /bin/bash -c "echo \$BASH_VERSION | sed -nre 's/^[^0-9]*(([0-9]+\.)*[0-9]+).*/\1/p'"
# ignore_errors: yes
# changed_when: false
# register: installed_bash_version
# - name: BASH - Report version
# debug:
# msg: "Installed Version: {{ installed_bash_version.stdout }}"
# - name: BASH - Download bash-{{ bash_version_asset_label }}.tar.gz
# ansible.builtin.get_url:
# #url: https://ftp.gnu.org/gnu/bash/bash-{{ bash_version_asset_label }}.tar.gz
# url: https://mirror.csclub.uwaterloo.ca/gnu/bash/bash-{{ bash_version_asset_label }}.tar.gz
# dest: /tmp/
# register: bash_download
# when: bash_version != installed_bash_version.stdout
# - name: BASH - Unpack bash-{{ bash_version_asset_label }}.tar.gz
# ansible.builtin.unarchive:
# src: "{{ bash_download.dest }}"
# dest: /tmp/
# when: bash_version != installed_bash_version.stdout
# - name: BASH - Run ./configure
# ansible.builtin.shell: ./configure
# args:
# chdir: /tmp/bash-{{ bash_version_asset_label }}
# creates: Makefile
# when: bash_version != installed_bash_version.stdout
# - name: BASH - Run make
# ansible.builtin.shell: make
# args:
# chdir: /tmp/bash-{{ bash_version_asset_label }}
# creates: Makefile
# when: bash_version != installed_bash_version.stdout
# - name: BASH - Run make prefix=/ install
# ansible.builtin.shell: make prefix=/ install
# args:
# chdir: /tmp/bash-{{ bash_version_asset_label }}
# when: bash_version != installed_bash_version.stdout
# Applications Directory
# ----------------------
- name: Create {{ local_applications_dir }} directory
ansible.builtin.file:
path: "{{ local_applications_dir }}"
state: directory
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: '0775'
when: local_applications_dir is not exists
# ffmpeg
# ------
- name: Create ffmpeg directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: '0775'
when: item is not exists and item is defined
loop: "{{ ffmpeg_dirs }}"
- name: ffmpeg - Check installed version
shell: "cat {{ ffmpeg_dirs[0] }}/ffmpeg.version 2>/dev/null"
ignore_errors: yes
changed_when: false
register: installed_ffmpeg_version
- name: ffmpeg - Check latest version
shell: |
curl -Ss "https://api.github.com/repos/nwjs-ffmpeg-prebuilt/nwjs-ffmpeg-prebuilt/tags" \
| jq -r '.[].name' \
| grep -E "^[v]?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$" \
| head -n 1
ignore_errors: yes
changed_when: false
register: latest_ffmpeg_version
- name: ffmpeg - Report Versions
debug:
msg: "Installed: {{ installed_ffmpeg_version.stdout}}, Latest: {{ latest_ffmpeg_version.stdout }}"
- name: Extract ffmpeg
ansible.builtin.unarchive:
src: "https://github.com/nwjs-ffmpeg-prebuilt/nwjs-ffmpeg-prebuilt/releases/download/{{ latest_ffmpeg_version.stdout }}/{{ latest_ffmpeg_version.stdout }}-linux-x64.zip"
dest: "{{ item }}"
remote_src: yes
loop: "{{ ffmpeg_dirs }}"
when: installed_ffmpeg_version.stdout != latest_ffmpeg_version.stdout
- name: Create ffmpeg version files
copy:
dest: "{{ item }}/ffmpeg.version"
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: '0775'
content: |
{{ latest_ffmpeg_version.stdout }}
loop: "{{ ffmpeg_dirs }}"
when: installed_ffmpeg_version.stdout != latest_ffmpeg_version.stdout
# yq
# --
- name: yq - Check installed version
shell: |
yq --version | awk '{print $NF}'
ignore_errors: yes
changed_when: false
register: installed_yq_version
- name: yq - Check latest version
shell: |
curl -Ss "https://api.github.com/repos/mikefarah/yq/tags" \
| jq -r '.[].name' \
| grep -E "^[v]?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$" \
| head -n 1
ignore_errors: yes
changed_when: false
register: latest_yq_version
- name: yq - Report Versions
debug:
msg: "Installed: {{ installed_yq_version.stdout}}, Latest: {{ latest_yq_version.stdout }}"
# Downloads multiple times... It's fine for this small package. For larger packages, dl to /tmp/foo.
# Cause: Multiple '-C' args are not permitted with tar.
- name: yq - Download and install yq
ansible.builtin.unarchive:
src: "https://github.com/mikefarah/yq/releases/download/{{ latest_yq_version.stdout }}/yq_{{ ansible_system|lower }}_{{ 'amd64' if ansible_architecture == 'x86_64' else ansible_architecture }}.tar.gz"
dest: /usr/local/bin
remote_src: yes
extra_opts:
- --transform
- s/yq_linux_amd64/yq/
- -C
- /usr/local/bin
- ./yq_linux_amd64
when: installed_yq_version.stdout != latest_yq_version.stdout
- name: yq - Download and install yq man page
ansible.builtin.unarchive:
src: "https://github.com/mikefarah/yq/releases/download/{{ latest_yq_version.stdout }}/yq_{{ ansible_system|lower }}_{{ 'amd64' if ansible_architecture == 'x86_64' else ansible_architecture }}.tar.gz"
dest: /usr/share/man/man1
remote_src: yes
extra_opts:
- -C
- /usr/share/man/man1
- yq.1
- name: Create ffmpeg version files
copy:
dest: "{{ item }}/ffmpeg.version"
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: '0775'
content: |
{{ latest_ffmpeg_version.stdout }}
loop: "{{ ffmpeg_dirs }}"
when: installed_ffmpeg_version.stdout != latest_ffmpeg_version.stdout
# pCloud
# ------
- name: Ensure {{ pcloud_local_share_dir }} directory exists
ansible.builtin.file:
path: "{{ pcloud_local_share_dir }}"
state: directory
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: '0775'
when: pcloud_local_share_dir is not exists
- name: Get pCloud Download URL
shell: |
curl -sS 'https://api.pcloud.com/getpublinkdownload?code=XZNtR95ZctUIq8zYVD7eSKotwGMx7kDWVtzV' \
| jq -r '"https://" + .hosts[0] + .path'
register: pcloud_dl_url
- name: Report pCloud Download URL
debug:
msg: "URL: {{ pcloud_dl_url.stdout }}"
- name: Download pCloud
ansible.builtin.get_url:
url: "{{ pcloud_dl_url.stdout }}"
force: true
dest: /usr/local/bin/pcloud
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: u=rwx,g=rwx,o=rx
register: pcloud_download
- name: Download pCloud Icon
ansible.builtin.get_url:
url: https://gist.githubusercontent.com/AlexAtkinson/86e7530f9a0b7c2744592e9fab54764b/raw/pcloud.svg
dest: /home/{{ ansible_env.SUDO_USER }}/.local/share/pCloud/pcloud.svg
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: u=rw,g=rw,o=r
# Defend against pcloud download process changes...
- name: Check pCloud Download File Type
shell: file -b {{ pcloud_download.dest }} | cut -d' ' -f1
register: pcloud_download_check
failed_when:
- '"ELF" not in pcloud_download_check.stdout'
- name: Create pCloud Menu Entry
copy:
dest: /home/{{ ansible_env.SUDO_USER }}/.local/share/applications/pcloud.desktop
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: '0775'
content: |
[Desktop Entry]
Icon=/home/{{ ansible_env.SUDO_USER }}/.local/share/pCloud/pcloud.svg
Exec=/usr/local/bin/pcloud %u
Version=1.0
Type=Application
Categories=Network
Name=pCloud
StartupWMClass=pcloud
MimeType=application/x-executable
X-GNOME-Autostart-enabled=true
StartupNotify=true
X-GNOME-Autostart-Delay=10
X-MATE-Autostart-Delay=10
X-KDE-autostart-after=panel
# qBittorrent
# -----------
- name: qBittorrent - Check installed version
shell: qBittorrent --version 2>/dev/null | cut -d' ' -f2
ignore_errors: yes
changed_when: false
register: installed_qbittorrent_version
- name: qBittorrent - Check latest version
shell: |
curl -s https://www.qbittorrent.org/download | \
grep "Current version" | \
awk '{print $NF}' | \
cut -d'<' -f1 | \
sed 's/^v//'
ignore_errors: yes
changed_when: false
register: latest_qbittorrent_version
- name: qBittorrent - Report Versions
debug:
msg: "Installed: {{ installed_qbittorrent_version.stdout }}, Latest: {{ latest_qbittorrent_version.stdout }}"
- name: Create {{ qbittorrent_dir }} directory
ansible.builtin.file:
path: "{{ qbittorrent_dir }}"
state: directory
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: '0775'
when: qbittorrent_dir is not exists
- name: Download qBittorrent
ansible.builtin.get_url:
url: https://gigenet.dl.sourceforge.net/project/qbittorrent/qbittorrent-appimage/qbittorrent-{{ latest_qbittorrent_version.stdout }}/qbittorrent-{{ latest_qbittorrent_version.stdout }}_x86_64.AppImage
dest: /usr/local/bin/qBittorrent
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: u=rwx,g=rwx,o=rx
when: qbittorrent_version != installed_qbittorrent_version.stdout
- name: Download qBittorrent Icon
ansible.builtin.get_url:
url: https://gist.githubusercontent.com/AlexAtkinson/f0bf8c89dda97ef42b5ba3575ab20363/raw/icon_qbittorrent.svg
dest: "{{ qbittorrent_icon }}"
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: u=rw,g=rw,o=r
when: qbittorrent_icon is not exists
- name: Create qBittorrent Menu Entry
copy:
dest: "{{ qbittorrent_menu_entry }}"
owner: "{{ ansible_env.SUDO_USER }}"
group: "{{ ansible_env.SUDO_USER }}"
mode: '0775'
content: |
[Desktop Entry]
Icon="{{ qbittorrent_icon }}"
Exec=/usr/local/bin/qBittorrent %u
Version=1.0
Type=Application
Categories=Network
Name=qBittorrent
StartupWMClass=qbittorrent
MimeType=application/x-executable
X-GNOME-Autostart-enabled=false
StartupNotify=true
X-GNOME-Autostart-Delay=10
X-MATE-Autostart-Delay=10
X-KDE-autostart-after=panel
when: qbittorrent_menu_entry is not exists
# Libre Office
# ------------
- name: Libre Office - Check installed version
shell: |
libreoffice --version \
| head -n 1 \
| grep -oE "[v]?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*(\.(0|[1-9][0-9]*))?)"
ignore_errors: yes
changed_when: false
register: installed_libreoffice_version
- name: Libre Office - Check latest version
shell: |
curl -sS http://downloadarchive.documentfoundation.org/libreoffice/old/latest/deb/x86_64/ \
| grep -o \"LibreOffice_.*_Linux_x86-64_deb.tar.gz\" \
| tr -d \" \
| grep -oE '[v]?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*(\.(0|[1-9][0-9]*))?)'
ignore_errors: yes
changed_when: false
register: latest_libreoffice_version
- name: Libre Office - Report Versions
debug:
msg: "Installed: {{ installed_libreoffice_version.stdout }}, Latest: {{ latest_libreoffice_version.stdout }}"
---
- name: Install Packages
hosts: localhost
gather_facts: true
vars:
# The private config url may be any private repo or "secret" gist and must contain
# a bash script which can be sourced.
private_config_git_url: [email protected]:AlexAtkinson/private_config.git
local_applications_dir: /home/{{ ansible_env.SUDO_USER }}/.local/share/applications
bash_version: 5.3.0
bash_version_asset_label: 5.3
ffmpeg_dirs:
- /usr/local/bin/ffmpeg
- /usr/lib/chromium-browser
- /usr/lib/x86_64-linux-gnu/opera
opera_version: 125.0.5729.15
pcloud_local_share_dir: /home/{{ ansible_env.SUDO_USER }}/.local/share/pCloud
qbittorrent_version: 5.3.0
qbittorrent_dir: /home/{{ ansible_env.SUDO_USER }}/.local/share/qBittorrent
qbittorrent_icon: /home/{{ ansible_env.SUDO_USER }}/.local/share/qBittorrent/qBittorrent.svg
qbittorrent_menu_entry: /home/{{ ansible_env.SUDO_USER }}/.local/share/applications/qbittorrent.desktop
qbittorrent_icon_url: https://gist.githubusercontent.com/AlexAtkinson/f0bf8c89dda97ef42b5ba3575ab20363/raw/icon_qbittorrent.svg
qbittorrent_dl_url: https://gigenet.dl.sourceforge.net/project/qbittorrent/qbittorrent-appimage/qbittorrent-{{ qbittorrent_version }}/qbittorrent-{{ qbittorrent_version }}_x86_64.AppImage
tasks:
- name: "Create temp directory for this build"
ansible.builtin.tempfile:
state: directory
prefix: "ansible_{{ now(utc=true, fmt='%Y-%m-%dT%H-%M-%SZ') }}_"
register: temp_dir
- name: "Create temp assets directory for use across multiple builds"
ansible.builtin.file:
path: /tmp/ansible_assets_dir
state: directory
mode: '0755'
register: temp_assets_dir
- name: "Apt: Install Packages"
ansible.builtin.include_tasks:
file: ansible_apt.yml
when: ansible_facts['os_family'] == 'Debian'
- name: "Permissions"
ansible.builtin.include_tasks:
file: ansible_permissions.yml
- name: "Custom Installs"
ansible.builtin.include_tasks:
file: ansible_custom_installs.yml
roles:
- geerlingguy.docker
- name: Ensure group 'docker' exists
ansible.builtin.group:
name: docker
state: present
- name: Add user '{{ ansible_env.SUDO_USER }}' to 'docker' group
ansible.builtin.user:
name: '{{ ansible_env.SUDO_USER }}'
groups: docker
append: true
- name: Ensure group 'nordvpn' exists
ansible.builtin.group:
name: nordvpn
state: present
- name: Add user '{{ ansible_env.SUDO_USER }}' to 'nordvpn' group
ansible.builtin.user:
name: '{{ ansible_env.SUDO_USER }}'
groups: nordvpn
append: true
#!/usr/bin/env bash
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Clone the GIST and run setup.sh
#
# AUTHOR : Alex Atkinson
# AUTHOR_EMAIL :
# AUTHOR_GITHUB : https://github.com/AlexAtkinson
# AUTHOR_LINKEDIN : https://www.linkedin.com/in/alex--atkinson
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
GIST='27b12f4dfda31b1b74fcab3fc9a6d192'
TMP_DIR=$(mktemp -d)
echo -e "TMP_DIR: $TMP_DIR"
cd "$TMP_DIR" || exit 1
git clone "[email protected]:${GIST}.git"
cd "$GIST" || exit 1
./setup.sh
#!/usr/bin/env bash
# shellcheck disable=2001
# ----------------------------------------------------------------------------------------------------------------------
#
# setup.sh
#
# SYNOPSIS
# Setup a local linux environment.
#
# TODO
# - Add Startup items
# - Replace some bash with facts. `ansible localhost -m setup`` (~/.ansible_facts)
# - ansible_system
# - ansible_distribution
# - ansible_architecture
# - Cleanup articles from previously written scripts that hint at multi-os support.
# - ffmpeg
# - plex ( mask permissions fix...)
# https://askubuntu.com/questions/150909/plex-wont-enter-my-home-directory-or-other-partitions
# - libre office
# https://www.libreoffice.org/download/download-libreoffice/
# sudo dpkg -i *.deb
# - fonts
# https://juliamono.netlify.app
# - VPN
# https://www.reddit.com/r/nordvpn/comments/1ccc2e5/split_tunnelling_on_linux_server
# - ClamAV
# https://docs.clamav.net/manual/Installing/Installing-from-source-Unix.html
# https://docs.clamav.net/manual/Installing/Add-clamav-user.html
# https://docs.clamav.net/faq/faq-freshclam.html
# - Rustup
# - ~/.config/tmux/tmux.conf
# set -g default-terminal "tmux-256color"
# set -ag terminal-overrides ",xterm-256color:RGB"
# - ~/.vimrc
# - Network Power Save
# /etc/NetworkManager/conf.d/default-wifi-powersave-on.conf Change 3 to 2
# [connection]
# wifi.powersave = 2
# - Fonts
# - Backup and restore
# - pCloud content
# - system dictionary
# - gitleaks
# - go install github.com/yannh/kubeconform/cmd/kubeconform@latest
#
# Disable hardware acceleration to stop site render problems opera://flags/#use-angle
# install nerdfonts to ~/.local/share/fonts
# Disable GPU Acceleration in Chromium browsers.
#
# - Opera opera://settings/system
#
# ----------------------------------------------------------------------------------------------------------------------
# Traps
# ----------------------------------------------------------------------------------------------------------------------
trap cd - || true EXIT
# ----------------------------------------------------------------------------------------------------------------------
# Environment Settings
# ----------------------------------------------------------------------------------------------------------------------
set u
set -o pipefail
# ----------------------------------------------------------------------------------------------------------------------
# Variables
# ----------------------------------------------------------------------------------------------------------------------
THIS_SCRIPT="${0##*/}"
DIR_NAME="${PWD##*/}"
PARENT_DIR_PATH="${PWD%/*}"
PARENT_DIR_NAME="${PARENT_DIR_PATH##*/}"
LOG_TO_FILE="true"
[[ "$LOG_TO_FILE" == "true" ]] && LOG_FILE="$HOME/pc_setup.log"
ANSIBLE_LOCALHOST_WARNING=False
ANSIBLE_INVENTORY_UNPARSED_WARNING=False
CONFIG_REPO='[email protected]:AlexAtkinson/pc_configs_personal.git'
# ----------------------------------------------------------------------------------------------------------------------
# Functions
# ----------------------------------------------------------------------------------------------------------------------
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Helper for `date` -- format for logging
# Outputs:
# - Date format compliant with ISO8601 + nano to the third
# place in UTC. IE: 1970-01-01T00:00:00.000Z
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function dts() { date --utc +'%FT%T.%3NZ'; }
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Syslog style error code handling with colors to improve
# DX.
# Notes:
# - User friendly way of achieving consistent log and
# script feedback.
# - Named loggerx to avoid clobbering logger if present.
# - There is no 9th severity level in RFC5424.
# - Delimiter sequence: space-hyphen-space ( - )
# - Accepts multi-line logging.
# IE: loggerx INFO "This is a
# multi-line
# log entry"
# Globals:
# LOG_TO_FILE
# LOG_FILE
# Arguments:
# - $1 Log Level
# - $2- Message
# Depends On:
# - function: dts
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# shellcheck disable=2034
function loggerx() {
local MSG LOG RAW S C C_EMERGENCY C_ALERT C_CRITICAL \
C_ERROR C_WARNING C_NOTICE C_INFO C_DEBUG C_SUCCESS
# Reverse lookup dict
C_EMERGENCY='\e[01;30;41m' # EMERGENCY
C_ALERT='\e[01;31;43m' # ALERT
C_CRITICAL='\e[01;97;41m' # CRITICAL
C_ERROR='\e[01;31m' # ERROR
C_WARNING='\e[01;33m' # WARNING
C_NOTICE='\e[01;30;107m' # NOTICE
C_INFO='\e[01;39m' # INFO
C_DEBUG='\e[01;97;46m' # DEBUG
C_SUCCESS='\e[01;32m' # SUCCESS
# Color lookup & spacing
case $1 in
"EMERGENCY") C="C_${1}"; S=$(printf "%-38s" '') ;;
"ALERT") C="C_${1}"; S=$(printf "%-34s" '') ;;
"CRITICAL") C="C_${1}"; S=$(printf "%-37s" '') ;;
"ERROR") C="C_${1}"; S=$(printf "%-34s" '') ;;
"WARNING") C="C_${1}"; S=$(printf "%-36s" '') ;;
"NOTICE") C="C_${1}"; S=$(printf "%-35s" '') ;;
"INFO") C="C_${1}"; S=$(printf "%-33s" '') ;;
"DEBUG") C="C_${1}"; S=$(printf "%-34s" '') ;;
"SUCCESS") C="C_${1}"; S=$(printf "%-36s" '') ;;
esac
# Final formatting
MSG=$(echo -e "$(dts) - ${!C}${1}\e[0m - $(sed 's/ */ /g'<<<"${*:2}")")
LOG=$(sed -z 's/\n$//g'<<<"${MSG}" | sed -z "s/\n/\n${S}/g")
RAW="$THIS_SCRIPT - $1 - $(sed 's/ */ /g'<<<"${*:2}")"
# Main Operation
if [[ "$LOG_TO_FILE" == "true" ]]; then
echo "$LOG" | tee -a "$LOG_FILE"
else
echo "$LOG"
fi
echo "$RAW" | logger
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# The `et` and `rc` functions are used to provide a simple
# and consistent method of including basic exit code
# validation and logging.
# - et Echo Task
# - rc Result Check
# Arguments:
# $TASK
# Depends On:
# - function: loggerx
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function et() { loggerx INFO "TASK START: $TASK..."; }
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Arguments:
# - $1 The expected exit code.
# - $2 Passed in $? exit code.
# - $3 If KILL is passed then exit with passed exit
# code.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function rc() {
local EXIT_CODE=$?
if [[ "$1" -eq "$EXIT_CODE" ]] ; then
loggerx SUCCESS "TASK END: $TASK."
else
loggerx CRITICAL "TASK END: $TASK (exit code: $EXIT_CODE)"
[[ "$3" == "KILL" ]] && exit "$2"
fi
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Report the name of the OS distribution.
# Outputs:
# The NAME field from the /etc/*-release file
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function check_os_distro() {
DISTRO=$(awk -F= '$1=="NAME" { gsub(/"/,"",$2); print $2 }' /etc/*-release)
export DISTRO
echo "$DISTRO"
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Report the architecture of the system.
# Outputs:
# Print the system architecture as defined by `uname -m`
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function check_os_arch() {
case $(uname -m) in
arm64|aarch64) ARCH="ARM64" ;;
armhf|armv7*) ARCH="ARM32_COMPAT" ;;
armv8*) ARCH="ARM64_COMPAT" ;;
i*86*) ARCH="x86" ;;
amd64|x86_64*) ARCH="x64" ;;
*) ARCH="unknown: $ARCH" ;;
esac
export ARCH
echo "$ARCH"
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Report the OS Type
# Outputs:
# The type of system as defined by the $OSTYPE variable.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function check_os_type() {
case "$OSTYPE" in
solaris*) OS_TYPE="SOLARIS" ;;
darwin*) OS_TYPE="OSX" ;;
linux*) OS_TYPE="LINUX" ;;
bsd*) OS_TYPE="BSD" ;;
msys*) OS_TYPE="WINDOWS" ;;
cygwin*) OS_TYPE="CYGWIN" ;;
*) OS_TYPE="unknown: $OSTYPE" ;;
esac;
export OS_TYPE
echo "$OS_TYPE"
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# DX friendly user prompts.
# Arguments:
# $1 Quoted prompt. IE: "Proceed?"
# $2 Default Option. IE: Y
# Outputs:
# Exit Code matching response.
# Example:
# ask "Proceed?" Y
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function ask {
while true; do
if [ "${2:-}" = "Y" ]; then
prompt="Y/n"
default=Y
elif [ "${2:-}" = "N" ]; then
prompt="y/N"
default=N
elif [ "${2:-}" = "Range" ]; then
prompt="${3:-}"
default=0
else
prompt="y/n"
default=
fi
read -rp $"$1 [$prompt]: " reply
if [ -z "$reply" ]; then
reply=$default
fi
case "$reply" in
Y*|y*|^[1-9][0-9]*$) return 0 ;;
N*|n*|0*) return 1 ;;
esac
done
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check SSH Authentication to Github
# Notes:
# - As a function to facilitate use with `while`
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function check_ssh_authentication_to_github() {
TASK="Test SSH Authentication to GitHub"; et
ssh -o "StrictHostKeyChecking accept-new" -T [email protected] 2>&1 | grep -q 'successfully authenticated'
rc 0
}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check SSH Authentication to CONFIG_REPO
# Notes:
# - As a function to facilitate use with `while`
# Arguments:
# $CONFIG_REPO
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function check_ssh_authentication_to_config_repo() {
TASK="Test SSH Authentication to Private Config Repo"; et
git ls-remote "$CONFIG_REPO" >/dev/null 2>&1
rc 0
}
# ----------------------------------------------------------------------------------------------------------------------
# Main Operations
# ----------------------------------------------------------------------------------------------------------------------
TASK="Confirm script execution"
if ! ask "This script configures a Debian based OS to the preferences of Alex Atkinson. Proceed?" Y; then
false; rc 0 KILL
fi
# A
TASK="Confirm installation of private configurations"
if ! ask "Additionally install private configurations" Y; then
false; rc 0 KILL
else
CONFIG_INSTALL=true
TASK="Prompt for configuration archive password"
read -rs -p "Enter configuration archive password: " CONFIG_ARCHIVE_PASS
rc 0
fi
TASK="Confirm sudo privilege"
loggerx WARNING "SUDO privilege required.
By proceeding you agree that you have reviewed the script content and trust the author.
Note specifically that this program is provided without any warranty or guarantee."
if ! ask "Confirm SUDO privilege" Y; then
false; rc 0 KILL
else
sudo true; rc 0
fi
TASK="Create SSH Key"; et
if [[ ! -f "$HOME/.ssh/id_ed25519" ]]; then
ssh-keygen -t ed25519 -C "[email protected]" -f "$HOME/.ssh/id_ed25519" -N "" -y
rc 0
else
loggerx NOTICE "SSH Key already exists. Skipping creation."
true; rc 0
fi
# Setup User bashrc
# ------------
TASK="Detect target rc file"; et
[[ -f $HOME/.bash_profile && ${SHELL##*/} =~ "bash" ]] && TARG="$HOME/.bash_profile" # MacOS Bash
[[ -f $HOME/.zshrc && ${SHELL##*/} =~ "zsh" ]] && TARG="$HOME/.zshrc" # MacOS ZSH
[[ -f $HOME/.bashrc && ${SHELL##*/} =~ "bash" ]] && TARG="$HOME/.bashrc" # Linux
rc 0 KILL
TARG_BACKUP="$TARG.$(date -u +"%FT%H-%M-%S").bak"
cp "$TARG" "$TARG_BACKUP" \
&& loggerx NOTICE "Your $TARG file has been backed up to $TARG_BACKUP!"
find "$HOME/" -type f -name "\.bashrc*.bak" | sort -r | tail -n +5 | tr '\n' '\0' | xargs -0 rm -f --
loggerx NOTICE "Only the five most recent $TARG.*.bak files have been kept."
TASK="Retrieve remote user bashrc file"; et
wget -q https://gist.githubusercontent.com/AlexAtkinson/bc765a0c143ab2bba69a738955d90abd/raw/.bashrc -O ~/.bashrc_user_gist; rc 0 KILL
START='# RCCTRLTEST - AUTOCONFIG START ----------------------------------------------------'
CONTENT=()
# shellcheck disable=SC2179
CONTENT+='# WARNING: Changes to this section will be overwritten.\n'
# shellcheck disable=SC2179
CONTENT+='# Last Updated: '"'$(date -u +"%FT%H-%M-%S")'"'\n'
# shellcheck disable=SC2179
CONTENT+='#\n'
# shellcheck disable=SC2179
CONTENT+='# NOTICE: Apply manual bashrc changes to ~\/.bashrc_user.\n'
# shellcheck disable=SC2179
CONTENT+='if \[ -f ~\/.bashrc_user \]; then\n'
# shellcheck disable=SC2179
CONTENT+=' . ~\/.bashrc_user\n'
# shellcheck disable=SC2179
CONTENT+='fi\n'
# shellcheck disable=SC2179
CONTENT+='# NOTICE: Apply remotely maintained bashrc changes to ~\/.bashrc_user_gist.\n'
# shellcheck disable=SC2179
CONTENT+='# Replace with any remote source as needed.\n'
# shellcheck disable=SC2179
CONTENT+='if \[ -f ~\/.bashrc_user_gist \]; then\n'
# shellcheck disable=SC2179
CONTENT+=' . ~\/.bashrc_user_gist\n'
# shellcheck disable=SC2179
CONTENT+='fi\n'
END='# RCCTRLTEST - AUTOCONFIG END ------------------------------------------------------'
TASK="Add control flags if not already present"; et
if [[ $(grep -c "$START" "$TARG") == 0 ]]; then
echo -e "\n$START\n\n$END" >> "$TARG"; rc 0
else
true; rc 0
fi
# WARNING: This sed command is invalid on MacOS. Ensure gsed is installed and aliased to sed.
# sed: 1: "...": unterminated substitute in regular expression
sed -ni "/${START}/{p;:a;N;/${END}/!ba;s/.*\n/${CONTENT[*]}/};p" "$TARG"; rc 0 KILL
loggerx SUCCESS "Your $TARG file has been updated!"
loggerx INFO "Run 'source $TARG' to make your commands available in this terminal session."
# Update Operating System
# -----------------------
# Ensure this section runs only on linux hosts.
if [[ $(check_os_type) != "LINUX" ]]; then
loggerx ERROR "The remainder of this script supports LINUX only."
exit 1
fi
if [[ ! -f /etc/debian_version ]]; then
loggerx ERROR "The remainder of this script supports Debian based distributions only."
exit 1
fi
TASK="System: apt update"; et
sudo apt update; rc 0 KILL
TASK="System: apt full-upgrade"; et
sudo apt full-upgrade -y ; rc 0 KILL
TASK="Update Operating System"; et
true; rc 0
# Install Prerequisites
# ---------------
TASK="Install packages"; et
sudo apt install \
ansible \
jq
rc 0 KILL
# Ansible
# -------
TASK="Ansible: Install Roles"; et
sudo ansible-galaxy role install geerlingguy.docker; rc 0 KILL
TASK="Ansible: Install Apt Packages"; et
sudo ansible-playbook ansible_main.yml; rc 0
# Private Configuration
# ---------------------
if [[ "$CONFIG_INSTALL" == "true" ]]; then
while ! check_ssh_authentication_to_github; do
loggerx ERROR "Could not authenticate to Github.
Add you SSH key (~/.ssh/id_ed25519) to your GitHub account at https://github.com/settings/keys.
REF: https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account"
ask "Retry?" Y || break
done
if check_ssh_authentication_to_config_repo; then
TASK="Ansible: Install Private Configuration"; et
sudo ansible-playbook ansible_private.yml; rc 0 KILL
else
loggerx ERROR "You do not have access to $CONFIG_REPO"
fi
fi
# Cleanup
# -------
loggerx INFO "Setup is complete."
TASK="Confirm computer restart"; et
if ask "Would you like to restart now" Y; then
shutdown -r 0
else
loggerx WARNING "Some applications may not function correctly without logging out/in, or restarting."
false; rc 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment