Itching for Answers

Itching for Answers
개나리 Forsythia

Ah, spring! The season of renewal, blooming flowers, and... allergy-itchy skin and eyes? Yes, you read that right. I find myself battling not with the complexities of network configurations but with the irritating discomfort of dry skin and a bit of python code. At least pythons don’t bite, am I right? But hey, I'm not one to let a little itchiness get me down, especially when the trees budding and the flowers—ah-ah-ah-choo!

As I sit at my keyboard, scratching away at my arms and neck (sorry for any disturbing visuals that statement conjures), I can't help my excitement for the changing weather outside. The sun shining, birds chirping—it's a welcomed change from the dreariness of winter. When I’m honest, winter is overrated; it’s great at first, but it wears on my spirits by mid-January, beginning of February at the latest. I never really have the same spark in winter that warmer weather ushers in. 

Anyway, despite the uplift spring brings, there's a technical challenge that's been nagging me to the core. You see, I've been working on a custom Python script to handle the synchronization of .ssh/config and /etc/hosts configurations across multiple devices. Sounds fancy, right? Well, it's been a bit of a bumpy ride, and it really shouldn’t be. After all, I am basically just building a very localized DNS listing.

I thought I had everything figured out—writing lines of code, testing, rewriting, retesting, scrapping, reimagining—but alas, it seems like there's always something that doesn't quite work as expected. Maybe it's a syntax error here, a logic flaw there, or perhaps just the universe reminding me that perfection is a myth. Or maybe it is spring kicking me out the door to go enjoy the 개나리 (Forsythia, pictured above) that are barely blooming. Regardless, I refuse to let this setback wet-blanket my enthusiasm.

So here I am, scratching my head both figuratively and literally, determined to make this code confirm to my will. And while I may not have all the answers just yet, I'm hopeful that with a little persistence and perhaps some input from fellow tech enthusiasts, I'll get there—wherever there happens to be.

Which brings me to you, dear reader. If you happen to be a seasoned Pythonista or a Linux expert, I welcome your insights and suggestions. Heck—I’d even settle for a few words of encouragement. After all, isn't that what community is all about? Sharing knowledge, lending a helping hand, and celebrating victories—no matter how small they may seem.

So why not embrace the springtime challenges with optimism and dogged determination (not to be confused with dogwood irritation).

doggone dogwood

Together, let's keep coding, keep exploring, and keep scratching that itch (figuratively, of course). And who knows, maybe we'll sort this code together sooner than we think.


Here is a few revisions of the code, so you can kinda get an idea of what I’m working with.

I started with python script to just create a simple sqlite3 database.

import sqlite3
Connect to the database
conn = sqlite3.connect('hosts.db')
c = conn.cursor()
Create table
c.execute('''CREATE TABLE IF NOT EXISTS hosts
             (id INTEGER PRIMARY KEY,
             hostname TEXT NOT NULL,
             ip_address TEXT NOT NULL,
             username TEXT,
             identity_file TEXT)''')
Commit changes and close connection
conn.commit()
conn.close()

And here is a somewhat working bit of code:

import sqlite3
import shutil
import os
from datetime import datetime

# Function to create backups of config and hosts files
def backup_files():
    now = datetime.now().strftime('%Y%m%d%H%M%S')
    shutil.copyfile('.ssh/config', f'.ssh/config.{now}')
    shutil.copyfile('/etc/hosts', f'/etc/hosts.{now}')

# Function to handle getting hosts from the database
def get_hosts():
    conn = sqlite3.connect('hosts.db')
    c = conn.cursor()
    c.execute('SELECT * FROM hosts')
    hosts = c.fetchall()
    conn.close()
    return hosts

# Function to handle adding hosts to the database
def add_host(hostname, ip_address, username=None, identity_file=None):
    conn = sqlite3.connect('hosts.db')
    c = conn.cursor()
    c.execute('INSERT INTO hosts (hostname, ip_address, username, identity_file) VALUES (?, ?, ?, ?)',
              (hostname, ip_address, username, identity_file))
    conn.commit()
    conn.close()

# Function to handle editing hosts in the database
def edit_host(hostname, new_ip_address=None, new_username=None, new_identity_file=None):
    conn = sqlite3.connect('hosts.db')
    c = conn.cursor()
    if new_ip_address:
        c.execute('UPDATE hosts SET ip_address = ? WHERE hostname = ?', (new_ip_address, hostname))
    if new_username:
        c.execute('UPDATE hosts SET username = ? WHERE hostname = ?', (new_username, hostname))
    if new_identity_file:
        c.execute('UPDATE hosts SET identity_file = ? WHERE hostname = ?', (new_identity_file, hostname))
    conn.commit()
    conn.close()

# Function to handle deleting hosts from the database
def delete_host(hostname):
    conn = sqlite3.connect('hosts.db')
    c = conn.cursor()
    c.execute('DELETE FROM hosts WHERE hostname = ?', (hostname,))
    conn.commit()
    conn.close()

# Main function
def main():
    backup_files()
    action = input("Would you like to: Get (1), Add (2), Edit (3), or Delete (4) hosts? ")
    
    if action == '1':
        hosts = get_hosts()
        for host in hosts:
            print(host)
    elif action == '2':
        hostname = input("Enter hostname: ")
        ip_address = input("Enter IP address: ")
        username = input("Enter username (optional): ")
        identity_file = input("Enter identity file (optional): ")
        add_host(hostname, ip_address, username, identity_file)
    elif action == '3':
        hostname = input("Enter hostname to edit: ")
        new_ip_address = input("Enter new IP address (leave blank to keep current): ")
        new_username = input("Enter new username (leave blank to keep current): ")
        new_identity_file = input("Enter new identity file (leave blank to keep current): ")
        edit_host(hostname, new_ip_address, new_username, new_identity_file)
    elif action == '4':
        hostname = input("Enter hostname to delete: ")
        delete_host(hostname)
    else:
        print("Invalid option")

if __name__ == "__main__":
    main()

If you followed all that, you will see that at this point in the code arch, we have a programmatically-generated database and a basic user interface to input hostnames, ip addresses, usernames, and identityfiles (useful for pointing to the location of public-keys for secure ssh connections). The script successfully generates a datetime stamped backup of the .ssh/config and /etc/hosts files.

Where I am having trouble is in writing out to the appropriate files (and in the correct format) the variables that users input into the database. In further code revisions, I have tried (and succeeded at) parsing the .ssh/config and etc/hosts files into json key-value pairs, but I haven’t been able to connect all the dots before getting the itch—er, uh, urge—to get outside. 

And with that, I will leave you with this: Do you even think this is a problem worth solving? Would you just run pihole or bind9 as a prebuilt DNS? What exactly is the problem I have set out to resolve (bonus points if you can tell me, but I’ll give you a hint: VPN)?

I’m going out to play—laters—and happy networking!

Keep us brewing!

Nothing good in IT ever happened without coffee.Thanks for your support!

Buy us a coffee