do-rsync.sh
cache
content/pages/resume.pdf
+.mypy_cache/
--- /dev/null
+#########################################################
+A workaround for running deja-dup as root in Ubuntu 20.04
+#########################################################
+
+:date: 2020-07-13
+:tags: hint, deja-dup, ubuntu
+:category: hints
+:author: Jude N
+
+I found a workaround for the `Duplicity fails to start`_ issue where :code:`'sudo deja-dup'` would fail with the python stacktrace mentioned in the launchpad ticket.
+
+The ticket was not very useful, so I started looking at the various files in the stacktrace and saw line from /usr/lib/python3/dist-packages/duplicity/backends/giobackend.py was within an :code:`"if u'DBUS_SESSION_BUS_ADDRESS' not in os.environ"` block.
+
+So I wondered what would happen if I let that environment variable pass into the sudo environment. I tried :code:`'sudo -E deja-dup'` as per `preserve the environment`_. This didn't result in a stacktrace, but it ended up running the backup as the normal non-root user, probably because the preserved environment included the USER and HOME variables along with the DBUS_SESSION_BUS_ADDRESS variable.
+
+Then I tried preserving just DBUS_SESSION_BUS_ADDRESS with :code:`'sudo --preserve-env=DBUS_SESSION_BUS_ADDRESS deja-dup'`, it worked as expected.
+
+So the hint here is that when presented with a stacktrace don't be afraid to "Use the Source, Jean Luc".
+
+.. image:: {static}/images/use-the-source-jean-luc.png
+ :align: center
+ :alt: A image of Patrick Stewart playing Gurney Halleck from David Lynch's Dune film, with the meme text 'Use the Source, Jean Luc'.
+
+
+.. _Duplicity fails to start: https://bugs.launchpad.net/ubuntu/+source/duplicity/+bug/1855736
+.. _preserve the environment: https://www.petefreitag.com/item/877.cfm
--- /dev/null
+################
+Expired CA Notes
+################
+
+:date: 2022-10-31
+:tags: hint, openssl, x509, expiration
+:category: hints
+:author: Jude N
+
+Recently, I ran some tests to see what would happen when my root CA cert expired, and what I'd need to do to update the cert.
+
+Spoiler alert: Updating the CA cert was not that hard...
+
+First I created a CA that expired in 2 hours using the new-ca.py code below:
+
+.. code-block:: python
+
+ from OpenSSL import crypto
+
+ #Following script will create a self signed root ca cert.
+ from OpenSSL import crypto, SSL
+ from os.path import join
+ import random
+
+ CN='expired-ca-test'
+ pubkey = "%s.crt" % CN #replace %s with CN
+ privkey = "%s.key" % CN # replcate %s with CN
+
+ pubkey = join(".", pubkey)
+ privkey = join(".", privkey)
+
+ k = crypto.PKey()
+ k.generate_key(crypto.TYPE_RSA, 2048)
+
+ # create a self-signed cert
+ cert = crypto.X509()
+ cert.get_subject().CN = CN
+ cert.set_serial_number(0)
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(7200) # CA is only good for 2 hours
+ cert.set_issuer(cert.get_subject())
+ cert.set_subject(cert.get_subject())
+ cert.set_version(2)
+ xt = crypto.X509Extension(b'basicConstraints',1,b'CA:TRUE')
+ cert.add_extensions((xt,))
+
+ cert.set_pubkey(k)
+ cert.sign(k, 'sha512')
+ pub=crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
+ priv=crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
+ open(pubkey,"wt").write(pub.decode("utf-8"))
+ open(privkey, "wt").write(priv.decode("utf-8") )
+
+This block is based on how the ancient certmaster program `created its CA`_.
+
+Then I created a expired-ca-test.srl with contents "01"
+
+.. code-block:: bash
+
+ > echo 01 > expired-ca-test.srl
+
+Then I issued a cert against this CA:
+
+.. code-block:: bash
+
+ > openssl genrsa -out pre-expired-example.key 4096
+ > openssl req -new -key pre-expired-example.key -out pre-expired-example.csr
+ > openssl x509 -req -days 365 -in pre-expired-example.csr -CA expired-ca-test.crt -CAkey expired-ca-test.key -CAserial expired-ca-test.srl -out pre-expired-example.crt
+ > openssl x509 -in pre-expired-example.crt -text
+ > openssl verify -verbose -CAfile expired-ca-test.crt pre-expired-example.crt
+ pre-expired-example.crt: OK
+
+Then I `waited 2 hours`_ and went back to check the certs:
+
+.. code-block:: bash
+
+ > openssl x509 -in expired-ca-test.crt -noout -enddate
+ notAfter=Oct 2 17:41:11 2022 GMT
+
+Then I tested what would happen if I tried verifying the cert signed with the expired CA:
+
+.. code-block:: bash
+
+ > openssl verify -verbose -CAfile expired-ca-test.crt pre-expired-example.crt
+ CN = expired-ca-test
+ error 10 at 1 depth lookup: certificate has expired
+ error pre-expired-example.crt: verification failed
+
+
+THIS FAILED. I thought previously signed keys would continue to verify against the expired CA but new certs wouldn't be created. Instead previously signed certs won't validate against the expired CA.
+
+Then I tried signing a new cert with the expired CA. Certainly this fail, right ?
+
+.. code-block:: bash
+
+ > openssl genrsa -out expired-example.key 4096
+ > openssl req -new -key expired-example.key -out expired-example.csr
+ > openssl x509 -req -days 365 -in expired-example.csr -CA expired-ca-test.crt -CAkey expired-ca-test.key -CAserial expired-ca-test.srl -out expired-example.crt
+
+THIS WORKED in that it created the cert, though verification still fails:
+
+.. code-block:: bash
+
+ > openssl verify -verbose -CAfile expired-ca-test.crt expired-example.crt
+ CN = expired-ca-test
+ error 10 at 1 depth lookup: certificate has expired
+ error expired-example.crt: verification failed
+
+
+Now lets see what happens if we update the CA cert with the update-ca.py script below.
+
+This is almost the same as the new-ca.py script above, except **the original CA key is reused instead of generating a new key**. Also **the CN and serial number need to be the same as the original expired CA cert**.
+
+Verification will fail if the CN or serial number values are not the same as the original CA, but unfortuanetly I didn't save the errors from when I tried using 'updated-ca-test' as the CN, or when I tried bumping up the serial number to 1.
+
+.. code-block:: python
+
+ from OpenSSL import crypto
+
+ #Following script will create a self signed root ca cert.
+ from OpenSSL import crypto, SSL
+ from os.path import join
+ import random
+
+ CN='updated-ca-test'
+ pubkey = "%s.crt" % CN #replace %s with CN
+ privkey = "%s.key" % CN # replcate %s with CN
+
+ pubkey = join(".", pubkey)
+ privkey = join(".", privkey)
+
+ # Instead of creating a new key, use the old CA's key
+ # nope: k = crypto.PKey()
+ # nope: k.generate_key(crypto.TYPE_RSA, 2048)
+ st_key=open('expired-ca-test.key', 'rt').read()
+ k = crypto.load_privatekey(crypto.FILETYPE_PEM, st_key)
+
+ # create a self-signed cert
+ cert = crypto.X509()
+ cert.get_subject().CN = 'expired-ca-test' # keep the same CN as the old CA cert
+ cert.set_serial_number(0) # keep the same serial number as the old CA cert
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(63072000) # CA is only good for 2 years
+ cert.set_issuer(cert.get_subject())
+ cert.set_subject(cert.get_subject())
+ cert.set_version(2)
+ xt = crypto.X509Extension(b'basicConstraints',1,b'CA:TRUE')
+ cert.add_extensions((xt,))
+
+ cert.set_pubkey(k)
+ cert.sign(k, 'sha512')
+ pub=crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
+ priv=crypto.dump_privatekey(crypto.FILETYPE_PEM, k)
+ open(pubkey,"wt").write(pub.decode("utf-8"))
+ open(privkey, "wt").write(priv.decode("utf-8") )
+
+Note that this code creates a updated-ca-test.key that's the same as expired-ca-test.key, so I could have continued using expired-ca-test.key in the cert creation below.
+
+.. code-block:: bash
+
+ > diff expired-ca-test.key updated-ca-test.key
+ > echo $?
+ 0
+
+
+Next I created an updated-ca-test.srl file. I could have continuned using expired-ca-test.srl
+
+.. code-block:: bash
+
+ > cp expired-ca-test.srl updated-ca-test.srl
+
+Now let's see if the new CA can be used to create a new cert:
+
+.. code-block:: bash
+
+ > openssl genrsa -out post-expired-example.key 4096
+ > openssl req -new -key post-expired-example.key -out post-expired-example.csr
+ > openssl x509 -req -days 365 -in post-expired-example.csr -CA updated-ca-test.crt -CAkey updated-ca-test.key -CAserial updated-ca-test.srl -out post-expired-example.crt
+ > openssl x509 -in post-expired-example.crt -text
+ > openssl verify -verbose -CAfile updated-ca-test.crt post-expired-example.crt
+ post-expired-example.crt: OK
+
+Now verify the old cert verifies using the new CA:
+
+.. code-block:: bash
+
+ > openssl verify -verbose -CAfile updated-ca-test.crt pre-expired-example.crt
+ pre-expired-example.crt: OK
+
+THIS WORKED. The updated CA could be used to verify both new and previous created certs Hurray !!
+
+Conclusion
+==========
+
+An expired/expiring root CA may be a hassle, but it's not catastrophic. The biggest pain should be pushingout the updated root CA everywhere the cert is being used in your environment. If you're using an orchestration/CM tool like Salt or Ansible, updating the root CA cert shouldn't be too bad, but remember to reload or restart any services using the cert to force the updated CA cert to read.
+
+Sources
+=======
+- https://serverfault.com/questions/306345/certification-authority-root-certificate-expiry-and-renewal
+- https://gist.github.com/mohanpedala/468cf9cef473a8d7610320cff730cdd1
+
+
+.. _`created its CA`: https://github.com/jude/certmaster/blob/master/certmaster/certs.py#L92
+.. _`waited 2 hours`: https://store.steampowered.com/app/1366540/Dyson_Sphere_Program/
+
+
--- /dev/null
+#####################################
+Podman/Testinfra/Salt Lessons Learned
+#####################################
+
+:date: 2022-11-13
+:tags: lessons,podman,testinfra,salt
+:category: lessons
+:author: Jude N
+
+Oh my, less than two years between posts. I'm on a roll !
+
+I've been looking into using `Podman`_ and `Testinfra`_ to test `Salt`_ states.
+
+I'd like to add some unit tests and a Containerfile to an existing repo of salt states, where running 'pytest' in the repo's workspace would spin up the container and run the tests against it, and then tear down the container.
+
+The tests would run 'salt state.apply' commands against the container, applying different sets of pillar data depending on the test.
+
+Project Directory Structure
+===========================
+
+First let's set up a directory structure for the project that includes the states, their tests, and any needed test data. In the case of salt states, the test dtaa will be pillar files and files served by ext_pillar. The directory structure below is what I ended up using:
+
+::
+
+ ├─ test_project repo
+ ├─── .git
+ ├─── env
+ ├────── ... testinfra egg
+ ├─── Containerfile
+ ├─── setup.cfg
+ ├─── tests
+ ├───── test_*.py
+ ├───── data
+ ├──────── ext_pillar
+ ├──────── pillar
+ ├────────── top.sls
+ ├────────── test_zero.sls
+ ├────────── test_one.sls
+ ├────────── ...
+ ├──────── top.sls
+ ├─── test_project
+ ├───── *.sls
+ ├───── *.jinja
+ ├───── templates
+ ├──────── *.jinja
+ ├───── files
+ ├───── ...
+
+
+Assuming all these files are stored in git, there's a .git directory from when you cloned the repo
+
+The 'env' directory is a python virtualenv under 'env', where the testinfra egg has been installed. You can skip the virtualenv if you're pulling in testinfra from a global package.
+
+Containerfile is, well a Podman Containerfile, and setup.cfg contains some pytest-specific settings.
+
+The tests directory is where the testinfra test\_\*.py files are stored.
+
+The tests/data/pillar directory will end up be mapped to the /srv/pillar directory in the test container. Similarly tests/data/ext_pillar will be mapped to /srv/ext_pillar.
+
+The salt-states directory includes the \*.sls and \*.jinja files, and any other salt-related subdirectories like 'templates', 'files', 'macros', etc. This directory will be mapped to /srv/salt/project in the container.
+
+Containerfile
+-------------
+
+The Containerfile I'm using for this project is below.
+
+.. code-block:: docker
+
+ # I'm using Ubuntu 20.4 for this project-under-test so pull in the stock Ubuntu image for that version
+ FROM ubuntu:focal
+ RUN apt-get update
+
+ # The stock image doesn't include curl, so install it and bootstrap salt
+ # Longterm, I would host the bootstrapping script internally in case that site disappeared.
+ RUN apt-get install -y curl
+ RUN curl -L https://bootstrap.saltproject.io | sh -s --
+
+ # Configure salt run as a masterless minion
+ RUN echo "file_client: local" > /etc/salt/minion.d/masterless.conf
+ RUN printf "local" > /etc/salt/minion_id
+
+ # Set up the /srv/salt environment
+ RUN mkdir -p /srv/salt
+ RUN mkdir -p /srv/ext_pillar/hosts/local/files
+ RUN printf "ext_pillar:\n - file_tree:\n root_dir: /srv/ext_pillar\n" >> /etc/salt/minion.d/masterless.conf
+
+ # Delay setting up /srv/salt/top.sls until the container starts. so PROJECT can be sent in as a ENV
+ RUN printf "printf \"base:\\n '*':\\n - \${PROJECT}\\n\" > /srv/salt/top.sls" >> /root/.bashrc
+
+ # Create a local user
+ RUN useradd local_user
+
+ # The Salt git states apparently assume git is already installed on the host, so install it.
+ RUN apt-get install -y git
+
+Building and verifying the saltmasterless:latest image
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Using this Containerfile, I built a saltmasterless:latest image:
+
+.. code-block:: shell-session
+
+ workstation:~/projects/test_project.git# podman build -t saltmasterless:latest .
+
+Then with this image, I can start a container that includes volumes mapping the tests/data/pillar ro /srv/pillar, tests/data/ext_pillar to /srv/ext_pillar, and test_project to /srv/salt:
+
+.. code-block:: shell-session
+
+ workstation:~/projects/test_project.git# podman run -it --env "PROJECT=test_project" -v ${PWD}/test_project:/srv/salt/test_project -v ${PWD}/tests/data/pillar:/srv/pillar -v ${PWD}/tests/data/ext_pillar:/srv/ext_pillar/hosts/local/files --name test_box --hostname local saltmasterless:latest
+ root@local:/#
+ root@local:/# find /srv
+ root@local:/# exit
+ workstation:~/projects/test_project.git# podman rm -f test_box
+
+setup.cfg
+---------
+The setup.cfg file is mostly used to tell pytest to ignore the salt states directory:
+
+.. code-block
+
+ [tool:pytest]
+ norecursedirs = test_project/files/*
+ addopts = -s
+ log_cli=true
+ log_level=NOTSET
+
+tests/data/pillar/top.sls
+-------------------------
+As mentioned above the tests/data/pillar directory will be mapped to /srv/pillar in the container, but let's look at the top.sls a little closer. From the Containerfile, /etc/salt/minion_id was set to 'local', so normally the top.sls file will end up using /srv/pillar/test_zero.sls for it's pillar data.
+
+But lets say we want to run a test with some other pillar data. In that case , in the test we'll use the salt-call '-id' argument to run the command as a different minion id. So with the top.sls file below, running 'salt-call --local -id=test_one state.apply' will use the test_one.sls pillar data instead of test_zero.sls
+
+.. code-block:: yaml+jinja
+
+ {{ saltenv }}:
+
+ '*':
+ - match: glob
+ - ignore_missing: True
+
+ 'local':
+ - test_zero
+
+ 'test_one':
+ - test_one
+
+ 'missing_mandatory_pillar_item':
+ - missing_mandatory_pillar_item
+
+
+tests/test_project.py host fixture
+----------------------------------
+
+The tests/test_project.py file includes a host fixture based on https://testinfra.readthedocs.io/en/latest/examples.html#test-docker-images. Note that the podman_cmd is pretty much the same as the command used above when testing the container. The cwd-related logic is because the -v args required full path names.
+
+.. code-block:: python
+
+ # scope='session' uses the same container for all the tests;
+ # scope='function' uses a new container per test function.
+ @pytest.fixture(scope='session')
+ def host(request):
+
+ cwd = os.getcwd()
+
+ podman_cmd = "podman run -d -it --env PROJECT=test_project -v ${PWD}/test_project:/srv/salt/test_project -v ${PWD}/tests/data/pillar:/srv/pillar -v ${PWD}/tests/data/ext_pillar:/srv/ext_pillar/hosts/local/files --name test_box --hostname local saltmasterless:latest bash"
+ podman_cmd = podman_cmd.replace("${PWD}",cwd)
+ podman_cmd_list = podman_cmd.split(' ')
+
+ # run a container
+ podman_id = subprocess.check_output(podman_cmd_list).decode().strip()
+ # return a testinfra connection to the container
+ yield testinfra.get_host("podman://" + podman_id)
+
+ # at the end of the test suite, destroy the container
+ subprocess.check_call(['podman', 'rm', '-f', podman_id])
+
+tests/test_project.py full salt run test
+----------------------------------------
+
+Here's a test that does a full salt state.apply on the container. This test is slow, since the container starts are with just salt and git installed, and the project-under-test is making a lot of changes. Note theuse of the '--local' argument to tell salt to try to pull data from a saltmaster.
+
+.. code-block:: python
+
+ def test_full_salt_run(host):
+ print('running salt-call state.apply. This will take a few minutes')
+ cmd_output = host.run('salt-call --state-output=terse --local state.apply')
+
+ print('cmd.stdout: ' + cmd_output.stdout)
+
+ assert cmd_output.rc == 0
+ assert cmd_output.stderr == ''
+
+tests/test_project.py alternative pillar data test
+---------------------------------------------------
+
+In this example, suppose ./test_project/map.jinja included a check like below:
+
+.. code-block:: jinja
+
+ {% if not salt['pillar.get']('mandatory_pillar_item') %}
+ {{ raise('mandatory_pillar_item is mandatory') }}
+ {% endif %}
+
+And then there's a 'missing_mandatory_pillar_item' in the ./test/data/pillar/top.sls as per above, and a ./test/data/pillar/missing_mandatory_pillar_item.sls file exists that's missing the mandatory pillar item.
+
+Then a test like below could force a salt run that uses this pillar data by using the '--id' argument as per below, and an assertion could check the error was raised.
+
+.. code-block:: python
+
+ def test_missing_mandatory_pillar_itemn(host):
+ print('running another salt-call state.apply with bad pillar data.')
+ cmd_output = host.run('salt-call --state-output=terse --local --id=missing_mandatory_pillar_item state.apply')
+ assert "mandatory_pillar_item is mandatory" in cmd_output.stderr
+ assert cmd_output.rc != 0
+
+Running the tests
+=================
+
+The tests are kicked off via 'pytest' like any other python project using pytest.`
+
+.. code-block:: shell-session
+
+ workstation:~/projects/test_project.git# source ./env/bin/activate
+ (env) workstation:~/projects/test_project.git# pytest
+ ...
+ ================================================================================ 3 passed in 333.88s (0:05:33) ================================================================================
+
+
+What's Next
+===========
+
+- Set up the salt bootstrapping so it'll work without having to reach out to bootstrap.saltproject.io
+- Move the host fixture out of /tests/test_project.py to ./tests/conftest.py
+- Speed up the tests. As mentioned above, a full 'salt state.apply' for a project can take a few minutes on my workstation
+
+.. _Podman: https://podman.io/
+.. _Testinfra: https://github.com/pytest-dev/pytest-testinfra
+.. _Salt: https://saltproject.io/
+
--- /dev/null
+TS Stands For Test Station
+##########################
+
+:date: 2022-12-17
+:tags: now-you-know,infrastructure,test-stations
+:category: lessons
+:author: Jude N
+
+Every now and then someone will come along and spraypaint yellow "TS"'s on the sidewalks around the neighorhood with arrows next to them. The arrows they lead to little square metal covers with a hole in the middle.
+
+From `99% Invisible`_, I figured it had something to do with the gas line since it was yellow, and that they was probably some sort of access point under the covers, but I couldn't figure out why they were using 'TS' instead something like 'GL' or 'GAS'.
+
+Recently I found one of the TS's pointing to a more informative cover:
+
+.. image:: {static}/images/test-station.png
+ :align: center
+ :alt: A yellow spraypainted 'TS' pointing to a metal cover including the text 'Test Station'.
+
+Apparently it's the test station for a `Cathodic Protection System`_.
+
+.. _99% Invisible: https://99percentinvisible.org/article/colorful-language-decoding-utility-markings-spray-painted-on-city-streets/
+.. _Cathodic Protection System: https://www.usbr.gov/tsc/training/webinars-corrosion/2014-02_TestingCathodicProtectionSystems/2014-02_TestingCathodicProtectionSystems_slides_508.pdf
--- /dev/null
+TakeMyBlood lessons learned
+###########################
+
+:date: 2022-08-22
+:tags: lessons
+:category: lessons
+:author: Jude N
+:status: draft
+
+I'm a frequent blood donor, but I don't like the `Red Cross donation site`_, and don't get me started on their Android app. I could go off on this for a while. but lets just say I've
+rage-quit setting up a donation appointment more than once.
+
+When I came across the Julia Evans `How to use undocumented web APIs`, I looked into whether I could come up with some way of finding donation sites that worked better for me.
+
+
+
+
+
+.. _Red Cross donation site: https://www.redcrossblood.org/give.html
+.. _How to use undocumented web APIs: https://jvns.ca/blog/2022/03/10/how-to-use-undocumented-web-apis/
Jude Nagurney
#############
-.. :date: 2019-07-14
+.. :date: 2023-08-12
.. :tags: resume
.. :category: resume
.. :author: Jude N
-.. :status: hidden
+.. :status: published
-| 1136 Forest Edge Drive
-| Reston, Virginia, 20190
-| Phone: (703) 829-0744
+| Northern Virginia
| jude.nagurney@gmail.com
=======
Technical Skills:
=================
-- **Languages** : Python, C/C++, Ruby, Perl, Java, SQL, Bash, lua, Expect, Tcl/Tk, UML OCL, COBOL
-- **Tools** : Puppet, Salt, Cobbler, Jenkins, emacs, vi, Jira, git, Gitlab, Docker, Mercurial, Subversion, Jira, SELinux
-- **Frameworks** : Django, Angular, Pylons, Rails, SqlAlchemy
-- **Operating Systems** : Linux (Ubuntu, Debian, RedHat, CentOS, Raspbian), Microsoft Windows, vxWorks, Solaris
-- **Databases** : PostgresSQL, MySQL, Oracle
-- **Standards Expertise** : SONET, SDH, TL1, LMP
+- **Languages** : Python, C/C++, Ruby, SQL, Bash, lua
+- **Tools** : Salt, SELinux, Jenkins, emacs, vi, Jira, git, Gitlab, Docker, podman, Automate Android app, Nagios
+- **Frameworks** : Django, Angular, Rails, SqlAlchemy
+- **Operating Systems** : Linux (Ubuntu, Debian, RedHat, CentOS, Rocky, Raspbian), Microsoft Windows
+- **Databases** : PostgresSQL, MySQL, Oracle, sqlite
----
| **Reston Virginia**
| **October 2016 - Present**
------------------
-Software Engineer
------------------
+--------------
+Staff Engineer
+--------------
+| ** February 2023 - Present
+
+Solution-oriented Staff Engineer adept at unraveling complex technical challenges, and fostering a culture of proactive problem-solving across projects.
+
+- Created a series of project-based orientation presentations to facilitate onboarding of new employees.
+
+- Assumed responsibility for a mission-critical dormant project, ensuring users had a dedicated point of contact.
+
+- Assumed responsibility for tagging and releasing unreleased software, ensuring its deployability.
+
+- Played an instrumental role in defining the staff engineer position within my current company.
+
+- Reviewed software changes to ensure code quality standards were upheld, and to distribute knowledge across the teams.
+
+------------------------
+Senior Software Engineer
+------------------------
+| ** October 2016 - February 2023
Developed and maintained Python-based software projects
-- Developed an SMS-based solution for the Netgear LTE Mobile Horspot Router
- This included a deep dive into the AT modem commands used for sending and receiving SMS messages.
+- Developed an SMS-based solution for the Netgear LTE Mobile Hotspot Router
+ Conducted a deep dive into AT modem commands essential for sending and receiving SMS messages.
- Developed SMS-based solutions using services such as Twilio, Plivo, Nexmo, and Vitelity.
+- Developed and maintained Salt states and Puppet manifests for various projects.
+
+- Developed iflows with the Automate app for controlling and monitoring Android phones.
+
- Wrote a Errbot plugin for reporting open merge requests that were waiting for peer reviews.
-- Worked on porting projects to Raspbian to run on a Raspberry Pi 3 Model B.
+- Worked on porting projects to Raspbian to run on a Raspberry Pi 3 Model B.
This included rebuilding packages for the arm7 architecture.
-- Developed and maintained Salt states and Puppet manifests for various projects.
-
-- Developed and maintained Jenkins continuous integration jobs for various projects.
+- Developed and maintained Jenkins continuous integration jobs for multiple projects.
Also proactively tracked down the root causes of build failures when the jobs failed.
+- Developd and monitored Nagious monitoring for the development network.
+
+- Provided Production support and troubleshooting.
+
+- Provided training and support for junior engineers.
+
---------------------
Applied Security Inc.
---------------------
- Extended a project to dynamically allocate AWS hosts based on system usage.
- Wrote Python code for sending and receiving SMS messages through Plivo and Twilio
-
+
- Developed and maintained Puppet manifests for development projects
..................................
Extended devops practices to cover security reviews
-- Introduced an SELinux strict policy workflow allowing developers to do most of the work associated with setting up a policy.
+- Introduced an SELinux strict policy workflow allowing developers to do most of the work associated with setting up a policy.
Previously all policy work was done by a single engineer. Now policy work can be distributed across the development team.
- Continued supporting puppet infrastructure for both the dev and ops environments, especially with respect to security-related changes.
TRANSLATION_FEED_ATOM = None
FEED_RSS = 'feeds/rss.xml'
FEED_ALL_RSS = 'feeds/all.rss.xml'
-CATEGORY_FEED_RSS = 'feeds/cat.%s.rss.xml'
-TAG_FEED_RSS = 'feeds/tag.%s.rss.xml'
+CATEGORY_FEED_RSS = 'feeds/cat.{slug}.rss.xml'
+TAG_FEED_RSS = 'feeds/tag.{slug}.rss.xml'
FEED_MAX_ITEMS = 20
# Blogroll
TAG_CLOUD_STEPS = 4
TAG_CLOUD_MAX_ITEMS = 100
-THEME = "../pelican-themes/pelican-mockingbird"
+#THEME = "../pelican-themes/pelican-mockingbird"
+THEME = "../pelican-themes/Flex"
+THEME_COLOR = 'dark'
+USE_LESS = False
+MAIN_MENU = True
+SITELOGO = None
+SITELOGO = '/blog/images/profile.png'
+FAVICON = '/blog/images/favicon-16x16.png'
+SUMMARY_MAX_LENGTH = 0
DISPLAY_PAGES_ON_MENU = True
PLUGIN_PATHS = ['../pelican-plugins']
PLUGINS = ['plantuml']
+
+MENUITEMS = (
+ ("Archives", "/blog/archives.html"),
+ ("Categories", "/blog/categories.html"),
+ ("Tags", "/blog/tags.html"),
+)
+
+SOCIAL = (
+ ("github", "https://github.com/jude"),
+ ("rss", "blog/feeds/all.rss.xml"),
+ ("mastodon", "https://aleph.land/@pwan")
+)
TRANSLATION_FEED_ATOM = None
FEED_RSS = 'feeds/rss.xml'
FEED_ALL_RSS = 'feeds/all.rss.xml'
-CATEGORY_FEED_RSS = 'feeds/cat.%s.rss.xml'
-TAG_FEED_RSS = 'feeds/tag.%s.rss.xml'
+CATEGORY_FEED_RSS = 'feeds/cat.{slug}.rss.xml'
+TAG_FEED_RSS = 'feeds/tag.{slug}.rss.xml'
FEED_MAX_ITEMS = 20
+DISPLAY_PAGES_ON_MENU = True
+
DELETE_OUTPUT_DIRECTORY = True
+PLUGIN_PATHS = ['../pelican-plugins']
+PLUGINS = ['plantuml']
+
+
# Following items are often useful when publishing
#DISQUS_SITENAME = ""