URI: 
       Removing old Anisble, updating hetzner Terraform. - infra - Terraform IoC for my remote (Hetzner) and local (Incus) servers.
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit d5730ddfc770cf4d3daaec8224f90d10eb986d4c
   DIR parent 635e9945ef2e0d2def7301381daec2115377891e
  HTML Author: Jay Scott <me@jay.scot>
       Date:   Wed, 17 Jul 2024 19:34:12 +0100
       
       Removing old Anisble, updating hetzner Terraform.
       
       Diffstat:
         M README                              |      45 +++++++------------------------
         D ansible/group_vars/all.yml          |       4 ----
         D ansible/group_vars/git.yml          |       4 ----
         D ansible/group_vars/gopher.yml       |      12 ------------
         D ansible/inventory.yml               |      10 ----------
         D ansible/main.yml                    |      21 ---------------------
         D ansible/roles/common/files/sshd_co… |       6 ------
         D ansible/roles/common/handlers/main… |       4 ----
         D ansible/roles/common/tasks/main.yml |      47 -------------------------------
         D ansible/roles/common/tasks/setup-D… |       5 -----
         D ansible/roles/common/vars/Debian.y… |       5 -----
         D ansible/roles/finger/files/list     |      16 ----------------
         D ansible/roles/finger/files/log      |       1 -
         D ansible/roles/finger/files/logo.txt |       7 -------
         D ansible/roles/finger/files/luser    |      29 -----------------------------
         D ansible/roles/finger/files/mcrae.t… |     222 ------------------------------
         D ansible/roles/finger/files/morris.… |     125 -------------------------------
         D ansible/roles/finger/files/nouser   |      19 -------------------
         D ansible/roles/finger/tasks/main.yml |      36 -------------------------------
         D ansible/roles/finger/vars/Debian.y… |       3 ---
         D ansible/roles/git/handlers/main.yml |       6 ------
         D ansible/roles/git/tasks/main.yml    |      31 -------------------------------
         D ansible/roles/git/templates/git-da… |      18 ------------------
         D ansible/roles/git/vars/Debian.yml   |       2 --
         D ansible/roles/gopher/handlers/main… |      12 ------------
         D ansible/roles/gopher/tasks/main.yml |      58 ------------------------------
         D ansible/roles/gopher/templates/geo… |      16 ----------------
         D ansible/roles/gopher/vars/Debian.y… |       4 ----
         D ansible/roles/gopher/vars/main.yml  |       5 -----
         D ansible/roles/stagit/handlers/main… |       5 -----
         D ansible/roles/stagit/tasks/main.yml |      46 -------------------------------
         D ansible/roles/stagit/templates/sta… |      38 -------------------------------
         D ansible/roles/stagit/vars/Debian.y… |       4 ----
         D ansible/roles/stagit/vars/main.yml  |       5 -----
         A remote/.terraform.lock.hcl          |      24 ++++++++++++++++++++++++
         A remote/checks.tf                    |       8 ++++++++
         A remote/cloudinit/main.yml           |      47 +++++++++++++++++++++++++++++++
         A remote/main.tf                      |      69 ++++++++++++++++++++++++++++++
         A remote/outputs.tf                   |       3 +++
         A remote/terraform.tfvars             |      53 ++++++++++++++++++++++++++++++
         A remote/variables.tf                 |      25 +++++++++++++++++++++++++
       
       41 files changed, 239 insertions(+), 861 deletions(-)
       ---
   DIR diff --git a/README b/README
       @@ -1,42 +1,17 @@
        
       -|> Terraform
       +        ___  __
       +| |\ | |__  |__)  /\
       +| | \| |    |  \ /~~\
        
       -Bootstrap Hetzner Cloud servers.
        
       -        Add public ssh key to account
       -        Provision X servers with desired configuration
       -        Create custom firewall rules
       -        Add reverse dns entry
       -        Userdata bootscript adding an Ansible user account
       -
       -If you actually want to use this for yourself then you might need to do
       -the following depending on your requirements.
       +---
        
       +The remote servers are hosted Hetzner Cloud servers and selfhosted are running
       +on Incus. If you actually want to use this for yourself then you might need to
       +do the following depending on your requirements.
        
                export HCLOUD_TOKEN='thisismylongasstoken'
        
       -
       -The user_data script is a standard cloud-init yaml config that creates
       -an Ansible user for further configuration the instances.
       -
       -
       -        terraform plan
       -        terraform apply
       -
       -
       -|> Ansible
       -
       -Playbook to install the following:
       -
       -  gopher
       -  git and git daemon
       -  stagit-gopher
       -  efingerd
       -
       -Apply all:
       -
       -        ansible-playbook -i inventory.yml main.yml
       -
       -Run tags
       -
       -        ansible-playbook -i inventory.yml main.yml --tags <git/gopher/common>
       +        tofu init
       +        tofu plan
       +        tofu apply
   DIR diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
       @@ -1,4 +0,0 @@
       -default:
       -  username: jay
       -  comment: Jay Scott
       -  hostname: jay.scot
   DIR diff --git a/ansible/group_vars/git.yml b/ansible/group_vars/git.yml
       @@ -1,4 +0,0 @@
       -git:
       -  user: git
       -  group: users
       -  base_path: /srv/git
   DIR diff --git a/ansible/group_vars/gopher.yml b/ansible/group_vars/gopher.yml
       @@ -1,12 +0,0 @@
       -geomyidae:
       -  user: jay
       -  group: users
       -  root_path: /srv/gopher
       -  git_branch: v0.69
       -
       -stagit_gopher:
       -  git_branch: 1.2
       -  cron_user: jay
       -  cron_group: users
       -  output_path: /srv/gopher
       -  repo_path: /srv/git
   DIR diff --git a/ansible/inventory.yml b/ansible/inventory.yml
       @@ -1,10 +0,0 @@
       -all:
       -  vars:
       -    ansible_user: jay
       -  children:
       -    git:
       -      hosts:
       -        jay.scot
       -    gopher:
       -      hosts:
       -        jay.scot
   DIR diff --git a/ansible/main.yml b/ansible/main.yml
       @@ -1,21 +0,0 @@
       -- hosts: all
       -  become: true
       -  roles:
       -    - role: common
       -  tags:
       -    - common
       -
       -- hosts: git
       -  become: true
       -  roles:
       -    - role: git
       -    - role: stagit
       -  tags:
       -    - git
       -
       -- hosts: gopher
       -  become: true
       -  roles:
       -    - role: gopher
       -  tags:
       -    - gopher
   DIR diff --git a/ansible/roles/common/files/sshd_config b/ansible/roles/common/files/sshd_config
       @@ -1,6 +0,0 @@
       -PasswordAuthentication no
       -ChallengeResponseAuthentication no
       -UsePAM yes
       -X11Forwarding no
       -Subsystem sftp        /usr/lib/openssh/sftp-server
       -PermitRootLogin no
   DIR diff --git a/ansible/roles/common/handlers/main.yml b/ansible/roles/common/handlers/main.yml
       @@ -1,4 +0,0 @@
       -- name: Restart sshd
       -  ansible.builtin.service:
       -    name: sshd
       -    state: restarted
   DIR diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml
       @@ -1,47 +0,0 @@
       ----
       -
       -- name: Include OS-specific variables
       -  ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
       -
       -- name: Include OS-specfic tasks
       -  ansible.builtin.include_tasks: "setup-{{ ansible_os_family }}.yml"
       -
       -- name: Install Common Packages
       -  ansible.builtin.package:
       -    name: "{{ common_packages }}"
       -    state: present
       -
       -- name: Add hardened SSH config
       -  ansible.builtin.copy:
       -    dest: /etc/ssh/sshd_config
       -    src: sshd_config
       -    owner: root
       -    group: root
       -    mode: 0600
       -  notify: Restart sshd
       -
       -- name: Add default user
       -  ansible.builtin.user:
       -    name: "{{ default.username }}"
       -    comment: "{{ default.comment }}"
       -    shell: /bin/bash
       -    group: users
       -
       -- name: Set authorized key
       -  ansible.posix.authorized_key:
       -    user: "{{ default.username }}"
       -    state: present
       -    key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"
       -
       -- name: Add default user to sudo
       -  community.general.sudoers:
       -    name: "local-{{ default.username }}"
       -    user: "{{ default.username }}"
       -    state: present
       -    nopassword: true
       -    commands: ALL
       -
       -- name: Set the hostname
       -  ansible.builtin.hostname:
       -    name: "{{ default.hostname }}"
       -    use: systemd
   DIR diff --git a/ansible/roles/common/tasks/setup-Debian.yml b/ansible/roles/common/tasks/setup-Debian.yml
       @@ -1,5 +0,0 @@
       -- name: Update apt cache.
       -  ansible.builtin.apt:
       -    update_cache: true
       -    upgrade: true
       -    cache_valid_time: 3600
   DIR diff --git a/ansible/roles/common/vars/Debian.yml b/ansible/roles/common/vars/Debian.yml
       @@ -1,5 +0,0 @@
       -common_packages:
       -  - htop
       -  - vim
       -  - finger
       -  - lynx
   DIR diff --git a/ansible/roles/finger/files/list b/ansible/roles/finger/files/list
       @@ -1,16 +0,0 @@
       -#!/bin/sh
       -
       -cat "/etc/efingerd/logo.txt"
       -
       -printf "\n\n"
       -printf "Welcome to jay.scot!\n"
       -printf "Uptime : %s\n\n" "$(uptime)"
       -
       -printf "Available Fingers:\n\n"
       -printf "\tusername ... get user info\n"
       -
       -printf "\n\n"
       -printf "Current Users:\n\n"
       -printf "\tJay Scott\t\t%s\n" "$(last jay -n1 -R --time-format full | head -n1)"
       -printf "\tRobert Morris\t\tmorris\t pts/0\t      Wed Nov  2 08:23:03 1988\t still logged in\n"
       -printf "\tWilliam McRae\t\tmcrae \t pts/1\t      Sun Apr  7 15:35:49 1985\t still logged in"
   DIR diff --git a/ansible/roles/finger/files/log b/ansible/roles/finger/files/log
       @@ -1 +0,0 @@
       -# nope, no logs
   DIR diff --git a/ansible/roles/finger/files/logo.txt b/ansible/roles/finger/files/logo.txt
       @@ -1,7 +0,0 @@
       -     ___  _______  __   __        _______  _______  _______  _______
       -    |   ||   _   ||  | |  |      |       ||       ||       ||       |
       -    |   ||  |_|  ||  |_|  |      |  _____||       ||   _   ||_     _|
       -    |   ||       ||       |      | |_____ |       ||  | |  |  |   |
       - ___|   ||       ||_     _| ___  |_____  ||      _||  |_|  |  |   |
       -|       ||   _   |  |   |  |   |  _____| ||     |_ |       |  |   |
       -|_______||__| |__|  |___|  |___| |_______||_______||_______|  |___|
   DIR diff --git a/ansible/roles/finger/files/luser b/ansible/roles/finger/files/luser
       @@ -1,29 +0,0 @@
       -#!/bin/sh
       -
       -if [ "$3" = "root" ]; then
       -        printf "Yeah, not going to happen."
       -elif [ "$3" = "git" ]; then
       -        printf "I am not a git, you are!"
       -else
       -        user_folder="/home/${3}"
       -
       -        if [ -f "${user_folder}/.header" ]; then
       -                cat "${user_folder}/.header"
       -                printf "\n"
       -        fi
       -
       -        if [ -f "${user_folder}/.plan" ]; then
       -                printf "Plan:\n"
       -                cat "${user_folder}/.plan"
       -                printf "\n"
       -        fi
       -
       -        if [ -f "${user_folder}/.project" ]; then
       -                printf "Project:\n"
       -                cat "${user_folder}/.project"
       -                printf "\n"
       -        fi
       -
       -fi
       -
       -exit 0
   DIR diff --git a/ansible/roles/finger/files/mcrae.txt b/ansible/roles/finger/files/mcrae.txt
       @@ -1,222 +0,0 @@
       -
       -T H E    M Y S T E R Y    O F
       -
       -                                        __   __        ___
       -|  | | |    |    |  /\   |\/|     |\/| /  ` |__)  /\  |__
       -|/\| | |___ |___ | /~~\  |  |     |  | \__, |  \ /~~\ |___
       -
       -
       -
       -Willie McRae (18 May 1923 – 7 April 1985) was a Scottish lawyer, orator,
       -naval officer, politician and anti-nuclear campaigner. In the Second
       -World War he served in the British Army and then the Royal Indian Navy.
       -He supported the Indian independence movement and for much of his life
       -was active in the Scottish National Party (SNP).
       -
       -McRae is remembered for his mysterious death, in which his car crashed
       -in a remote part of the Scottish Highlands and he was found shot in the
       -head with a revolver. The official verdict was undetermined.
       -
       -
       -|> Life
       -
       -
       -McRae was born in Carron, Falkirk, where his father was an electrician.
       -McRae edited a local newspaper in Grangemouth at the same time as
       -reading history at the University of Glasgow, from which he gained
       -a first-class degree. In the Second World War he was commissioned into
       -the Seaforth Highlanders but transferred to the Royal Indian Navy, in
       -which he became a lieutenant commander and aide-de-camp to Admiral Lord
       -Mountbatten. He supported the Indian independence movement.
       -
       -After the war McRae returned to the University of Glasgow and graduated
       -again, this time in law.[1] He authored the maritime law of Israel and
       -was an emeritus professor of the University of Haifa.
       -After his death a forest of 3,000 trees was planted in Israel in his
       -memory.
       -
       -McRae became a solicitor and an SNP activist. In both of the 1974
       -General Elections and in the 1979 General Election he stood for
       -Parliament as the SNP candidate for Ross and Cromarty. In October 1974
       -he only lost to the Conservative Hamish Gray by 633 votes, but in 1979
       -Gray's majority increased to 4,735. In the latter year he also contested
       -the SNP leadership, coming third in a three-way contest with 52 votes to
       -Stephen Maxwell's 117 votes and winner Gordon Wilson's 530 votes.
       -
       -McRae was a vocal critic of the British nuclear lobby. Early in the
       -1980s he was a key figure in a campaign against the United Kingdom
       -Atomic Energy Authority plans to dispose of nuclear waste in the
       -Mullwharchar area of the Galloway Hills. Representing the SNP in
       -a public inquiry, McRae asked difficult questions of the UKAEA and
       -famously declared at one meeting that "nuclear waste should be stored
       -where Guy Fawkes put his gunpowder." The authority's plans were
       -rejected, and McRae was credited with "single-handedly" preventing the
       -area from becoming a nuclear waste dump.
       -
       -
       -|> Death
       -
       -
       -On 5 April 1985 McRae left his Glasgow flat at 18:30 to spend the
       -weekend at his cottage at Ardelve near Dornie, Ross-shire. He was not
       -seen again until the next morning around 10:00, when two Australian
       -tourists saw his maroon Volvo saloon car on a moor a short distance from
       -the junction of the A887 and A87 roads Bun Loyne, Glenmoriston,
       -Inverness-shire. The car was straddling a burn about 90 feet (27 m) from
       -the road. The tourists flagged down the next car to pass, whose driver
       -turned out to be a doctor, Dorothy Messer, accompanied by her fiancé as
       -well as David Coutts, an SNP Dundee councillor who knew McRae.
       -
       -It was discovered that McRae was in the car. His hands were "folded on
       -his lap", his head was "slumped on his right shoulder", and there was
       -a "considerable amount of blood on his temple". He was not wearing
       -a seat belt.
       -
       -Another car was sent to call the emergency services. Dr Messer examined
       -McRae and found that he was still alive and breathing. She noted that
       -one of his pupils was dilated, indicating the possibility of brain
       -damage, and estimated that he had been in that state for 10 hours.
       -
       -McRae was removed by ambulance to Raigmore Hospital, Inverness,
       -accompanied by Dr Messer. After admission it was decided to transfer him
       -to Aberdeen Royal Infirmary. At Aberdeen it was realised that the
       -incident was more than a road accident; six hours after he had been
       -found, a nurse washing his head discovered what appeared to be the entry
       -wound of a gunshot. An X-ray confirmed that McRae had been shot above
       -his right ear and a bullet was detected in his head. His brain was
       -severely damaged and his vital functions very weak. The next day, Sunday
       -7 April, after consultation with his next of kin, McRae's life-support
       -machine was switched off.
       -
       -
       -|>Investigation
       -
       -
       -The investigation was headed by Chief Superintendent Andrew Lister of
       -Northern Constabulary CID. Despite no weapon having yet been found,
       -McRae's car was moved at 12:00 on 7 April. It later transpired that the
       -police had kept no record of the precise location where the car had been
       -found, and the position stated by them was later found to be 1 mile (1.6
       -km) in error, and was corrected by a witness who had been present at the
       -scene.
       -
       -A weapon was found the next day, in the burn over which the car had been
       -discovered, 60 feet (18 m) from the vehicle. It was a Smith & Wesson .22
       -calibre revolver containing two spent cartridges and five remaining
       -rounds.
       -
       -
       -|> Controversy
       -
       -
       -Although it was ruled at the time by authorities that McRae's death was
       -undetermined, aspects of the investigation remain disputed, some
       -claiming that the distance from McRae's car at which the gun was found
       -and the lack of fingerprints on it rendered a suicide not credible.
       -
       -At the time of his death, McRae had been working to counter plans to
       -dump nuclear waste from the Dounreay Nuclear Power Development
       -Establishment into the sea. Due to his house being burgled on repeated
       -occasions prior to his death, he had taken to carrying a copy of the
       -documents relating to his Dounreay work with him at all times. They were
       -not found following his death, and the sole other copy which was kept in
       -his office was stolen when it was burgled, no other items being
       -taken.
       -
       -Neither McRae's medical reports nor the post-mortem data have been
       -released to the public and there was no fatal accident inquiry.
       -
       -
       -|> Aftermath
       -
       -
       -Winnie Ewing – then President of the SNP and herself an accomplished
       -lawyer – was directed by the SNP's National Executive Committee (NEC) to
       -conduct an internal investigation for the party to come to a conclusion
       -as to whether Ewing "was satisfied or dissatisfied with the official
       -version that he committed suicide". Having been refused access to police
       -records of the investigation and rebuffed by both the Lord Advocate and
       -the Procurator Fiscal in her attempts to conduct private, confidential
       -meetings with them, Ewing, as she later wrote, came "up against a brick
       -wall".[10] Ewing reported to the SNP NEC that she was not satisfied with
       -the official account of suicide: "I do not know what happened, but
       -I think it is important that the truth emerges, despite the time that
       -has passed. Why the State refuses to let the truth be known is
       -a pertinent question."
       -
       -In 1991 Channel 4 broadcast a "Scottish Eye" documentary investigating
       -the mysterious circumstances of McRae's death. It found evidence to
       -suggest that McRae had been under surveillance by UK intelligence
       -services and that his death had likely involved foul play.
       -
       -In 2005 Winnie Ewing's son Fergus, by then an MSP, requested a meeting
       -with Elish Angiolini, Solicitor General for Scotland, to discuss
       -allegations that have persisted that McRae was under surveillance at the
       -time of his death. The request was rebuffed, with Angiolini claiming
       -that he had not been under surveillance and that she was satisfied that
       -a thorough investigation into the case had been carried out.
       -
       -In July 2006 a retired police officer, Iain Fraser, who was working as
       -a private investigator at the time of McRae's death, claimed that he had
       -been anonymously employed to keep McRae under surveillance only weeks
       -before he died. In November 2006 an episode of the Scottish Television
       -show Unsolved examined the circumstances of McRae's death.
       -
       -In November 2010 John Finnie, then SNP group leader on Highland Council
       -and a former police officer, wrote to the Lord Advocate urging her to
       -reinvestigate McRae's death and release any details so far withheld.
       -Finnie's request was prompted by the release the previous month of
       -further details concerning the death of David Kelly.[14] In January 2011
       -the Crown Office requested the files on the case from Northern
       -Constabulary.
       -
       -Also in November 2010 Donald Morrison, a former Strathclyde Police
       -officer, alleged that McRae had been "under surveillance" by both
       -Special Branch and MI5. Morrison had collaborated with former colleague
       -Iain Fraser to discover more about McRae's death. Morrison called for an
       -enquiry into McRae's death and promised that he would give it a sworn
       -affidavit that MI5 was involved.
       -
       -In July 2014 two unconnected plays by George Gunn and Andy Paterson
       -about McRae's life and death, both coincidentally titled 3,000 Trees,
       -were staged at the Edinburgh Festival Fringe. One of the plays explored
       -his anti-nuclear campaigning, links with nationalist radicals and
       -allegations that Special Branch and MI5 were surveilling him.
       -
       -In November 2014 a Scottish Sunday Express front-page article alleged
       -that McRae had uncovered evidence of the alleged paedophile ring in
       -Westminster during the 1980s. The article suggests he may have been
       -murdered and that the evidence he possessed was stolen at the time of
       -his death.
       -
       -In April 2015 there was a campaign to have a Fatal Accident Inquiry
       -(FAI) on McRae's death. It attracted 6,500 signatures in 5 days.
       -
       -The petition eventually collected over 13,000 signatures and was handed
       -in, in June 2015. The Crown Office rejected the proposal to hold a Fatal
       -Accident Inquiry.
       -
       -On the Easter weekend of April 2015, the 30th anniversary of McRae's
       -death, Scotland on Sunday ran a story claiming that McRae's Volvo was
       -moved back to the crash site by Northern Constabulary in an attempt to
       -hide that the car had been moved before the bullet had been found
       -– accounting for the discrepancies relating to the gun's distance from
       -the car.
       -
       -On the same day, one of the journalists involved started crowdfunding
       -for a book on the case titled '30 Years of Silence'.
       -
       -Following the rejection of the petition for a Fatal Accident Inquiry by
       -the Crown Office, a "Justice For Willie" Campaign group was set up by
       -Mark MacNicol. The campaign decided to launch their own investigation
       -since no official inquiry was forthcoming. They hired two private
       -investigators to re-interview original witnesses from the time of Willie
       -McRae's death. The results were published in November 2016, and the
       -campaign were unable to find any new evidence to undermine the official
       -suicide verdict.
       -
       -In October 2018, fresh doubt on the official verdict was raised again by
       -a nurse who claims to have treated Willie McRae at Foresterhill Hospital
       -in Aberdeen. Katharine Mcgonigal disputed that the bullet wound was to
       -the right temple, as the post-mortem claimed, and said it was instead to
       -the back of the neck.
   DIR diff --git a/ansible/roles/finger/files/morris.txt b/ansible/roles/finger/files/morris.txt
       @@ -1,125 +0,0 @@
       -
       -T  H  E
       -  ___ ___  ___  ____  ____  ____ _____     __    __  ___  ____  ___ ___
       - |   |   |/   \|    \|    \|    / ___/    |  |__|  |/   \|    \|   |   |
       - | _   _ |     |  D  )  D  )|  (   \_     |  |  |  |     |  D  ) _   _ |
       - |  \_/  |  O  |    /|    / |  |\__  |    |  |  |  |  O  |    /|  \_/  |
       - |   |   |     |    \|    \ |  |/  \ |    |  `  '  |     |    \|   |   |
       - |   |   |     |  .  \  .  \|  |\    |     \      /|     |  .  \   |   |
       - |___|___|\___/|__|\_|__|\_|____|\___|      \_/\_/  \___/|__|\_|___|___|
       -
       -
       -
       -The Morris worm or Internet worm of November 2, 1988, is one of the oldest
       -computer worms distributed via the Internet, and the first to gain significant
       -mainstream media attention. It resulted in the first felony conviction in the
       -US under the 1986 Computer Fraud and Abuse Act. It was written by a graduate
       -student at Cornell University, Robert Tappan Morris, and launched on November
       -2, 1988, from the Massachusetts Institute of Technology network.
       -
       -
       -|> Architecture
       -
       -
       -The worm was created by Morris simply to see if it could be done,
       -and was released from the Massachusetts Institute of Technology (MIT) in the
       -hope of suggesting that its creator studied there, instead of Cornell. Morris
       -later became a tenured professor at MIT in 2006. The worm's creator Robert
       -Tappan Morris is the son of cryptographer Robert Morris, who worked at the NSA
       -at the time.
       -
       -The worm exploited several vulnerabilities of targeted systems, including:
       -
       -  A hole in the debug mode of the Unix sendmail program
       -
       -  A buffer overflow or overrun hole in the finger network service
       -
       -  The transitive trust enabled by people setting up network logins with no
       -  password requirements via remote execution (rexec) with Remote Shell (rsh),
       -  termed rexec/rsh
       -
       -  The worm exploited weak passwords. Morris's exploits became generally
       -  obsolete due to decommissioning rsh (normally disabled on untrusted networks),
       -  fixes to sendmail and finger, widespread network filtering, and improved
       -  awareness of weak passwords.
       -
       -Though Morris did not intend for the worm to be actively destructive, instead
       -seeking to merely highlight the weaknesses present in many networks of the
       -time, an unintentional consequence of Morris's coding resulted in the worm
       -being more damaging and spreadable than originally planned. It was initially
       -programmed to check each computer to determine if the infection was already
       -present, but Morris believed that some system administrators might counter this
       -by instructing the computer to report a false positive. Instead, he programmed
       -the worm to copy itself 14% of the time, regardless of the status of infection
       -on the computer. This resulted in a computer potentially being infected
       -multiple times, with each additional infection slowing the machine down to
       -unusability. This had the same effect as a fork bomb, and crashed the computer
       -several times.
       -
       -The main body of the worm can only infect DEC VAX machines running 4BSD,
       -alongside Sun-3 systems. A portable C "grappling hook" component of the worm
       -was used to download the main body parts, and the grappling hook runs on other
       -systems, loading them down and making them peripheral victims.
       -
       -
       -|> Coding mistake
       -
       -
       -Morris's coding mistake, in instructing the worm to replicate itself regardless
       -of a computer's reported infection status, transformed the worm from a
       -potentially harmless intellectual and computing exercise into a viral
       -denial-of-service attack. Morris's inclusion of the rate of copy within the
       -worm was inspired by Michael Rabin's mantra of randomization.
       -
       -The resulting level of replication proved excessive, with the worm spreading
       -rapidly, infecting some computers several times. Rabin would eventually comment
       -that Morris "should have tried it on a simulator first".
       -
       -
       -|> Effects
       -
       -
       -During the Morris appeal process, the US court of appeals estimated the cost of
       -removing the virus from each installation was in the range of $200–53,000.
       -Possibly based on these numbers, Clifford Stoll of Harvard estimated for the US
       -Government Accountability Office that the total economic impact was between
       -$100,000 and $10,000,000. Stoll, a systems administrator known for discovering
       -and subsequently tracking the hacker Markus Hess three years earlier, helped
       -fight the worm, writing in 1989 that "I surveyed the network, and found that
       -two thousand computers were infected within fifteen hours. These machines were
       -dead in the water—useless until disinfected. And removing the virus often took
       -two days." Stoll commented that the worm showed the danger of monoculture,
       -because "If all the systems on the ARPANET ran Berkeley Unix, the virus would
       -have disabled all fifty thousand of them."
       -
       -It is usually reported that around 6,000 major UNIX machines were infected by
       -the Morris worm. However, Morris's colleague Paul Graham claimed, "I was there
       -when this statistic was cooked up, and this was the recipe: someone guessed
       -that there were about 60,000 computers attached to the Internet, and that the
       -worm might have infected ten percent of them." Stoll estimated that "only a
       -couple thousand" computers were affected, writing that "Rumors have it that
       -[Morris] worked with a friend or two at Harvard's computing department (Harvard
       -student Paul Graham sent him mail asking for 'Any news on the brilliant
       -project')."
       -
       -The Internet was partitioned for several days, as regional networks
       -disconnected from the NSFNet backbone and from each other to prevent
       -recontamination while cleaning their own networks.
       -
       -The Morris worm prompted DARPA to fund the establishment of the CERT/CC at
       -Carnegie Mellon University, giving experts a central point for coordinating
       -responses to network emergencies. Gene Spafford also created the Phage mailing
       -list to coordinate a response to the emergency.
       -
       -Morris was tried and convicted of violating United States Code Title 18 (18
       -U.S.C. § 1030), the Computer Fraud and Abuse Act, in United States v. Morris.
       -After appeals, he was sentenced to three years' probation, 400 hours of
       -community service, and a fine of US$10,050 (equivalent to $20,000 in 2021) plus
       -the costs of his supervision. The total fine ran to $13,326, which included a
       -$10,000 fine, $50 special assessment, and $3,276 cost of probation oversight.
       -
       -The Morris worm has sometimes been referred to as the "Great Worm", due to the
       -devastating effect it had on the Internet at that time, both in overall system
       -downtime and in psychological impact on the perception of security and
       -reliability of the Internet. The name was derived from the "Great Worms" of
       -Tolkien: Scatha and Glaurung.
   DIR diff --git a/ansible/roles/finger/files/nouser b/ansible/roles/finger/files/nouser
       @@ -1,19 +0,0 @@
       -#!/bin/sh
       -
       -if [ "$3" = "morris" ]; then
       -        cat "/etc/efingerd/morris.txt"
       -elif [ "$3" = "mcrae" ]; then
       -        cat "/etc/efingerd/mcrae.txt"
       -else
       -
       -        cat <<EOF
       -
       -        You tried to finger non-existent user!!!
       -        Your attempt is logged and sent to Scotland Yard, MI5 and the DLVA..
       -
       -        Expect a visit soon.
       -
       -        Just joking, it went to /dev/null
       -EOF
       -
       -fi
   DIR diff --git a/ansible/roles/finger/tasks/main.yml b/ansible/roles/finger/tasks/main.yml
       @@ -1,36 +0,0 @@
       ----
       -- name: Include OS-specific variables
       -  ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
       -
       -- name: Install required packages
       -  ansible.builtin.package:
       -    name: "{{ common_packages }}"
       -    state: present
       -
       -- name: Create efingerd config directory
       -  ansible.builtin.file:
       -    path: /etc/efingerd
       -    state: directory
       -    mode: '0755'
       -
       -- name: Copy efingerd scripts
       -  ansible.builtin.copy:
       -    src: "{{ item }}"
       -    dest: "/etc/efingerd/{{ item }}"
       -    owner: root
       -    group: root
       -    mode: '0755'
       -  loop:
       -    - list
       -    - logo.txt
       -    - morris.txt
       -    - mcrae.txt
       -    - log
       -    - luser
       -    - nouser
       -
       -- name: Enable inetd
       -  ansible.builtin.systemd:
       -    name: inetd
       -    state: started
       -    daemon_reload: true
   DIR diff --git a/ansible/roles/finger/vars/Debian.yml b/ansible/roles/finger/vars/Debian.yml
       @@ -1,3 +0,0 @@
       -common_packages:
       -  - openbsd-inetd
       -  - efingerd
   DIR diff --git a/ansible/roles/git/handlers/main.yml b/ansible/roles/git/handlers/main.yml
       @@ -1,6 +0,0 @@
       ----
       -- name: Restart git-daemon
       -  ansible.builtin.systemd:
       -    name: git-daemon
       -    state: started
       -    daemon_reload: true
   DIR diff --git a/ansible/roles/git/tasks/main.yml b/ansible/roles/git/tasks/main.yml
       @@ -1,31 +0,0 @@
       ----
       -- name: Include OS-specific variables
       -  ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
       -
       -- name: Install required packages
       -  ansible.builtin.package:
       -    name: "{{ common_packages }}"
       -    state: present
       -
       -- name: Added git service account
       -  ansible.builtin.user:
       -    name: "{{ git.user }}"
       -    comment: "Git Service Account"
       -    home: "{{ git.base_path }}"
       -    shell: /usr/bin/git-shell
       -    group: "{{ git.group }}"
       -
       -- name: Set initial authorized key
       -  ansible.posix.authorized_key:
       -    user: git
       -    state: present
       -    key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"
       -
       -- name: Create systemd service file
       -  ansible.builtin.template:
       -    src: templates/git-daemon.service.j2
       -    dest: /lib/systemd/system/git-daemon.service
       -    owner: root
       -    group: root
       -    mode: "0644"
       -  notify: Restart git-daemon
   DIR diff --git a/ansible/roles/git/templates/git-daemon.service.j2 b/ansible/roles/git/templates/git-daemon.service.j2
       @@ -1,18 +0,0 @@
       -[Unit]
       -Description=Start Git Daemon
       -
       -[Service]
       -ExecStart=/usr/bin/git daemon --reuseaddr --base-path={{ git.base_path }} {{ git.base_path }}
       -
       -Restart=always
       -RestartSec=500ms
       -
       -StandardOutput=syslog
       -StandardError=syslog
       -SyslogIdentifier=git-daemon
       -
       -User={{ git.user }}
       -Group={{ git.group }}
       -
       -[Install]
       -WantedBy=multi-user.target
   DIR diff --git a/ansible/roles/git/vars/Debian.yml b/ansible/roles/git/vars/Debian.yml
       @@ -1,2 +0,0 @@
       -common_packages:
       -  - git
   DIR diff --git a/ansible/roles/gopher/handlers/main.yml b/ansible/roles/gopher/handlers/main.yml
       @@ -1,12 +0,0 @@
       ----
       -- name: Restart geomyidae
       -  ansible.builtin.systemd:
       -    name: geomyidae
       -    state: started
       -    enabled: true
       -    daemon_reload: true
       -
       -- name: Install geomyidae
       -  community.general.system.make:
       -    chdir: "{{ build_path }}"
       -    target: install
   DIR diff --git a/ansible/roles/gopher/tasks/main.yml b/ansible/roles/gopher/tasks/main.yml
       @@ -1,58 +0,0 @@
       ----
       -- name: Include OS-specific variables.
       -  ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
       -
       -- name: Install required packages
       -  ansible.builtin.package:
       -    name: "{{ common_packages }}"
       -    state: present
       -
       -- name: Git checkout
       -  ansible.builtin.git:
       -    repo: "{{ source_repo }}"
       -    dest: "{{ build_path }}"
       -    force: false
       -    version: "{{ geomyidae.git_branch }}"
       -  register: repo_clone
       -  failed_when:
       -    - repo_clone.failed
       -    - not 'Local modifications exist in the destination' in repo_clone.msg
       -
       -- name: Disable TLS options
       -  ansible.builtin.replace:
       -    path: "{{ build_path }}/Makefile"
       -    regexp: '^TLS_CFLAGS = -DENABLE_TLS'
       -    replace: '#TLS_CFLAGS = -DENABLE_TLS'
       -  when: disable_tls
       -
       -- name: Disable TLS flags
       -  ansible.builtin.replace:
       -    path: "{{ build_path }}/Makefile"
       -    regexp: '^TLS_LDFLAGS = -ltls'
       -    replace: '#TLS_LDFLAGS = -ltls'
       -  when: disable_tls
       -
       -- name: Build geomyidae
       -  community.general.system.make:
       -    chdir: "{{ build_path }}"
       -  notify: Install geomyidae
       -
       -- name: Flush handlers
       -  ansible.builtin.meta: flush_handlers
       -
       -- name: Create root directory
       -  ansible.builtin.file:
       -    path: "{{ geomyidae.root_path }}"
       -    owner: "{{ geomyidae.user }}"
       -    group: "{{ geomyidae.group }}"
       -    mode: 0755
       -    state: directory
       -
       -- name: Create systemd service file
       -  ansible.builtin.template:
       -    src: templates/geomyidae.service.j2
       -    dest: /lib/systemd/system/geomyidae.service
       -    owner: root
       -    group: root
       -    mode: '0644'
       -  notify: Restart geomyidae
   DIR diff --git a/ansible/roles/gopher/templates/geomyidae.service.j2 b/ansible/roles/gopher/templates/geomyidae.service.j2
       @@ -1,16 +0,0 @@
       -[Unit]
       -Description=Geomyidae Gopher Server
       -After=network.target
       -Wants=network.target
       -StartLimitBurst=5
       -StartLimitIntervalSec=1
       -
       -[Service]
       -Type=forking
       -Restart=on-abnormal
       -RestartSec=1
       -User=root
       -ExecStart=/usr/local/bin/geomyidae -v 0 -b {{ geomyidae.root_path }} -p 70 -n -u {{ geomyidae.user }} -g {{ geomyidae.group }}
       -
       -[Install]
       -WantedBy=multi-user.target
   DIR diff --git a/ansible/roles/gopher/vars/Debian.yml b/ansible/roles/gopher/vars/Debian.yml
       @@ -1,4 +0,0 @@
       -common_packages:
       -  - git
       -  - build-essential
       -  - libssl-dev
   DIR diff --git a/ansible/roles/gopher/vars/main.yml b/ansible/roles/gopher/vars/main.yml
       @@ -1,5 +0,0 @@
       ----
       -
       -build_path: /tmp/src/geomyidae
       -source_repo: git://r-36.net/geomyidae
       -disable_tls: true
   DIR diff --git a/ansible/roles/stagit/handlers/main.yml b/ansible/roles/stagit/handlers/main.yml
       @@ -1,5 +0,0 @@
       ----
       -- name: Install stagit
       -  community.general.system.make:
       -    chdir: "{{ build_path }}"
       -    target: install
   DIR diff --git a/ansible/roles/stagit/tasks/main.yml b/ansible/roles/stagit/tasks/main.yml
       @@ -1,46 +0,0 @@
       ----
       -- name: Include OS-specific variables.
       -  ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
       -
       -- name: Install required packages
       -  ansible.builtin.package:
       -    name: "{{ common_packages }}"
       -    state: present
       -
       -- name: Git checkout
       -  ansible.builtin.git:
       -    repo: "{{ source_repo }}"
       -    dest: "{{ build_path }}"
       -    force: false
       -    version: "{{ stagit_gopher.git_branch }}"
       -  register: repo_clone
       -  failed_when:
       -    - repo_clone.failed
       -    - not 'Local modifications exist in the destination' in repo_clone.msg
       -
       -- name: Enable old libgit2 workaround
       -  ansible.builtin.replace:
       -    path: "{{ build_path }}/Makefile"
       -    regexp: '^#STAGIT_CFLAGS'
       -    replace: 'STAGIT_CFLAGS'
       -  when: enable_old_libgit2
       -
       -- name: Build from source
       -  community.general.system.make:
       -    chdir: "{{ build_path }}"
       -  notify: Install stagit
       -
       -- name: Create stagit run script
       -  ansible.builtin.template:
       -    src: templates/stagit_create.sh.j2
       -    dest: "/home/{{ stagit_gopher.cron_user }}/stagit_create.sh"
       -    owner: "{{ stagit_gopher.cron_user }}"
       -    group: "{{ stagit_gopher.cron_group }}"
       -    mode: "755"
       -
       -- name: Adding script to cron
       -  ansible.builtin.cron:
       -    name: "run stagit script"
       -    user: "{{ stagit_gopher.cron_user }}"
       -    minute: "*/5"
       -    job: "/home/{{ stagit_gopher.cron_user }}/stagit_create.sh"
   DIR diff --git a/ansible/roles/stagit/templates/stagit_create.sh.j2 b/ansible/roles/stagit/templates/stagit_create.sh.j2
       @@ -1,38 +0,0 @@
       -#!/bin/sh
       -
       -reposdir="{{ stagit_gopher.repo_path }}"
       -gopherdir="{{ stagit_gopher.output_path }}"
       -stagitdir="/git"
       -destdir="${gopherdir}/${stagitdir}"
       -
       -# remove /'s at the end.
       -stagitdir=$(printf "%s" "${stagitdir}" | sed 's@[/]*$@@g')
       -
       -rm -rf "{{ stagit_gopher.output_path }}/git/*"
       -
       -/usr/local/bin/stagit-gopher-index -b "${stagitdir}" "${reposdir}/"*.git/ >"${destdir}/index.gph"
       -
       -# make files per repo.
       -for dir in "${reposdir}/"*/; do
       -        # strip .git suffix.
       -        r=$(basename "${dir}")
       -        d=$(basename "${dir}" ".git")
       -        printf "%s... " "${d}"
       -
       -        mkdir -p "${destdir}/${d}"
       -        cd "${destdir}/${d}" || continue
       -
       -        if [ "$d" != "pass" ]; then
       -                /usr/local/bin/stagit-gopher -b "${stagitdir}/${d}" \
       -                        -u "gopher://jay.scot/1/git/$d/" "${reposdir}/${r}"
       -
       -                # symlinks
       -                ln -sf log.gph index.gph
       -
       -                echo "done"
       -        else
       -                echo "skipping"
       -        fi
       -done
       -
       -
   DIR diff --git a/ansible/roles/stagit/vars/Debian.yml b/ansible/roles/stagit/vars/Debian.yml
       @@ -1,4 +0,0 @@
       -common_packages:
       -  - git
       -  - build-essential
       -  - libgit2-dev
   DIR diff --git a/ansible/roles/stagit/vars/main.yml b/ansible/roles/stagit/vars/main.yml
       @@ -1,5 +0,0 @@
       ----
       -
       -build_path: /tmp/src/stagit-gopher
       -source_repo: git://git.codemadness.org/stagit-gopher
       -enable_old_libgit2: false
   DIR diff --git a/remote/.terraform.lock.hcl b/remote/.terraform.lock.hcl
       @@ -0,0 +1,24 @@
       +# This file is maintained automatically by "tofu init".
       +# Manual edits may be lost in future updates.
       +
       +provider "registry.opentofu.org/hetznercloud/hcloud" {
       +  version     = "1.47.0"
       +  constraints = "1.47.0"
       +  hashes = [
       +    "h1:aqEPcSpaWhKqbMs7c7Pf5ot6Tye7ntRitWsuNGPRPfk=",
       +    "zh:0759f0c23d0e59baab3382320eef4eb314e0c5967b6ef67ff07135da07a97b34",
       +    "zh:0e9ca84c4059d6d7e2c9f13d3c2b1cd91f7d9a47bedcb4b80c7c77d536eff887",
       +    "zh:17a033ac4650a39ddacf3265a449edabaea528f81542c4e63e254272d5aac340",
       +    "zh:2997c76a500e42b7519b24fa1f8646d9baab70c68277f80394560d3e1fd06e6d",
       +    "zh:37f3fe7bb34cac63c69123e43e5426bab75816b3665dbe7125276a8d2ee6b2d8",
       +    "zh:45d4b04dc470f24ad96c1c0b6636ea5422c659004f3e472c863bc50130fabf25",
       +    "zh:46df99d972a78af6875565e53a73df66d870c474a20cd90e9e0a3092aa25197f",
       +    "zh:4b5bb8d49366ad895c6c767efe16a1b8143802414abfe3fdb1184cbbecf424eb",
       +    "zh:55c6199eb401c4b0a6c948ceac8b50f352e252e1c985903ed173bf26ad0f109e",
       +    "zh:7b6efe897bffa37248064155a699e67953350b5b9a5476456c0160ce59254557",
       +    "zh:7bc004bcb649ce1ec70e2cf848392e10a1edbcbf11b3292a4cc5c5d49bd769e4",
       +    "zh:e1b17b7595f158fbb3021afa8869b541b5c10bdd2d8d2b2b3eaa82200b104ddd",
       +    "zh:f741ca40e8e99a3e4114ad108ea2b5a5bccbedb008326c7f647f250580e69c0e",
       +    "zh:fae9c7f8d08a447bb0972529f6db06999c35391046320206041a988aeca6b54c",
       +  ]
       +}
   DIR diff --git a/remote/checks.tf b/remote/checks.tf
       @@ -0,0 +1,8 @@
       +check "server_status" {
       +  assert {
       +    condition = alltrue([
       +      for k, v in var.nodes : hcloud_server.this[k].status == "running"
       +    ])
       +    error_message = "Server status check failed."
       +  }
       +}
   DIR diff --git a/remote/cloudinit/main.yml b/remote/cloudinit/main.yml
       @@ -0,0 +1,47 @@
       +#cloud-config
       +
       +ssh_pwauth: false
       +hostname: jay.scot
       +timezone: Europe/London
       +package_update: true
       +
       +packages:
       +  - caddy
       +  - git
       +  - git-daemon-sysvinit
       +  - unattended-upgrades
       +
       +users:
       +  - name: jay
       +    groups: users,wheel
       +    sudo: ALL=(ALL) NOPASSWD:ALL
       +    shell: /bin/bash
       +    lock_passwd: true
       +    ssh_authorized_keys:
       +      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDLmKYxwXTbyRWLG0S24RTpyfyBO6AL8Dcy0XvVZ97Do
       +
       +  - name: git
       +    shell: /usr/bin/git-shell
       +    homedir: /srv/git
       +    ssh_authorized_keys:
       +      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDLmKYxwXTbyRWLG0S24RTpyfyBO6AL8Dcy0XvVZ97Do
       +
       +write_files:
       +  - path: /etc/default/git-daemon
       +    permissions: '0644'
       +    content: |
       +      GIT_DAEMON_ENABLE=true
       +      GIT_DAEMON_USER=git
       +      GIT_DAEMON_BASE_PATH=/srv/git
       +      GIT_DAEMON_DIRECTORY=/srv/git
       +      GIT_DAEMON_OPTIONS="--export-all"
       +
       +  - path: /etc/caddy/Caddyfile
       +    permissions: '0644'
       +    content: |
       +      jay.scot {
       +        tls me@jay.scot
       +        root * /srv/www
       +        encode gzip
       +        file_server
       +      }
   DIR diff --git a/remote/main.tf b/remote/main.tf
       @@ -0,0 +1,69 @@
       +terraform {
       +  required_version = ">=1.7.0"
       +  required_providers {
       +    hcloud = {
       +      source  = "hetznercloud/hcloud"
       +      version = "1.47.0"
       +    }
       +  }
       +}
       +
       +resource "hcloud_ssh_key" "this" {
       +  for_each = var.public_ssh_keys
       +
       +  name       = each.key
       +  public_key = file(each.value)
       +}
       +
       +
       +resource "hcloud_firewall" "this" {
       +  for_each = var.firewall_rules
       +
       +  name = each.key
       +
       +  dynamic "rule" {
       +    for_each = each.value.rules
       +
       +    content {
       +      description = rule.key
       +      direction   = rule.value.direction
       +      protocol    = rule.value.protocol
       +      source_ips  = rule.value.source_ips
       +      port        = rule.value.port
       +    }
       +  }
       +}
       +
       +
       +resource "hcloud_server" "this" {
       +  for_each = var.nodes
       +
       +  name         = each.key
       +  image        = each.value.image
       +  server_type  = each.value.server_type
       +  location     = each.value.location
       +  labels       = each.value.labels
       +  ssh_keys     = [hcloud_ssh_key.this[each.value.public_key].id]
       +  user_data    = file(each.value.user_data)
       +  firewall_ids = [hcloud_firewall.this[each.key].id]
       +
       +  public_net {
       +    ipv4_enabled = each.value.ipv4
       +    ipv6_enabled = each.value.ipv6
       +  }
       +
       +  lifecycle {
       +    postcondition {
       +      condition     = self.status == "running"
       +      error_message = "Instance must be running."
       +    }
       +  }
       +}
       +
       +resource "hcloud_rdns" "this" {
       +  for_each = hcloud_server.this
       +
       +  server_id  = each.value.id
       +  ip_address = each.value.ipv4_address
       +  dns_ptr    = var.nodes[each.key].reverse_dns
       +}
   DIR diff --git a/remote/outputs.tf b/remote/outputs.tf
       @@ -0,0 +1,3 @@
       +output "public_ip_address" {
       +  value = { for name, server in hcloud_server.this : name => server.ipv4_address }
       +}
   DIR diff --git a/remote/terraform.tfvars b/remote/terraform.tfvars
       @@ -0,0 +1,53 @@
       +public_ssh_keys = {
       +  main = "~/.ssh/id_ed25519.pub"
       +}
       +
       +firewall_rules = {
       +  "main" = {
       +    rules = {
       +      ssh = {
       +        direction  = "in"
       +        protocol   = "tcp"
       +        source_ips = ["0.0.0.0/0", "::/0"]
       +        port       = "22"
       +      }
       +      git = {
       +        direction  = "in"
       +        protocol   = "tcp"
       +        source_ips = ["0.0.0.0/0", "::/0"]
       +        port       = "9418"
       +      }
       +      http = {
       +        direction  = "in"
       +        protocol   = "tcp"
       +        source_ips = ["0.0.0.0/0", "::/0"]
       +        port       = "80"
       +      }
       +      https = {
       +        direction  = "in"
       +        protocol   = "tcp"
       +        source_ips = ["0.0.0.0/0", "::/0"]
       +        port       = "443"
       +      }
       +
       +    }
       +  }
       +
       +}
       +
       +nodes = {
       +  "main" = {
       +    image       = "debian-12"
       +    location    = "hel1",
       +    server_type = "cx22",
       +    reverse_dns = "jay.scot"
       +    user_data   = "cloudinit/main.yml"
       +    public_key  = "main"
       +    ipv4        = true
       +    ipv6        = true
       +    labels = {
       +      git    = "true"
       +      gemini = "true"
       +    }
       +  }
       +}
   DIR diff --git a/remote/variables.tf b/remote/variables.tf
       @@ -0,0 +1,25 @@
       +
       +variable "public_ssh_keys" {
       +  description = "Public SSH key location to attach to instance."
       +  type        = map(any)
       +}
       +
       +variable "firewall_rules" {
       +  description = "Firewall configuration settings."
       +  type        = map(any)
       +}
       +
       +variable "nodes" {
       +  description = "Configuration settings for each required node."
       +  type = map(object({
       +    image       = string
       +    location    = string
       +    server_type = string
       +    reverse_dns = string
       +    user_data   = string
       +    public_key  = string
       +    labels      = map(any)
       +    ipv4        = bool
       +    ipv6        = bool
       +  }))
       +}