<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mike Lockhart</title>
    <description>The latest articles on DEV Community by Mike Lockhart (@sinewalker).</description>
    <link>https://dev.to/sinewalker</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F196711%2F573842b1-b203-4c29-b0e4-3855025be95b.png</url>
      <title>DEV Community: Mike Lockhart</title>
      <link>https://dev.to/sinewalker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sinewalker"/>
    <language>en</language>
    <item>
      <title>Use Jupyter Notebooks for bash scripting</title>
      <dc:creator>Mike Lockhart</dc:creator>
      <pubDate>Mon, 10 Mar 2025 20:18:48 +0000</pubDate>
      <link>https://dev.to/sinewalker/use-jupyter-notebooks-for-bash-scripting-n6o</link>
      <guid>https://dev.to/sinewalker/use-jupyter-notebooks-for-bash-scripting-n6o</guid>
      <description>&lt;p&gt;I'm experimenting with using Jupyter to capture Linux server commands in a Notebook while I figure out doing GitLab Geo setup. It's complicated, so I want to &lt;a href="https://milosophical.me/blog/2015/4-bit-rules-of-computing-part-1.html" rel="noopener noreferrer"&gt;keep good notes&lt;/a&gt;, but copy/paste from the terminal to a text editor is a chore I'd rather not deal with. Instead I would like to keep a literate-programming style notebook. I learnt recently that there is a &lt;a href="https://pypi.org/project/bash_kernel/" rel="noopener noreferrer"&gt;Jupyter kernel for running bash&lt;/a&gt; instead of python, and that would be ideal.&lt;/p&gt;

&lt;p&gt;This post describes how to set up using Jupyter Notebooks on a remote host, and then running and editing notebooks using Visual Studio Code's support for Jupyter.&lt;/p&gt;

&lt;p&gt;You can &lt;em&gt;also do this without VSCode&lt;/em&gt;, by SSH tunnelling a local web browser to the host. That would be better in cases where users' &lt;code&gt;/home&lt;/code&gt; volume size is restricted. I briefly discuss this too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install Python, Jupyter, and the Bash kernel
&lt;/h2&gt;

&lt;p&gt;First we need an environment for running Jupyter. I did try this globally, since I'm using a dedicated host, but it works better as a virtual environment ("venv").&lt;/p&gt;

&lt;p&gt;This server runs Ubuntu, so the software package installation is specific to that OS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the python-venv package
&lt;/h3&gt;

&lt;p&gt;In Ubuntu, Python is separated into multiple &lt;code&gt;.deb&lt;/code&gt; packages, and the python virtual environment functions are in &lt;code&gt;pythonX.Y-venv&lt;/code&gt;.  Don't worry about the specific version in the package name: if you install &lt;code&gt;python3-venv&lt;/code&gt; then APT will get the latest version available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;python3 python3-venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reading package lists... Done
Building dependency tree       
Reading state information... Done
python3 is already the newest version (3.8.2-0ubuntu2).
The following NEW packages will be installed:
  python3-venv
0 upgraded, 1 newly installed, 0 to remove and 5 not upgraded.
Need to get 1228 B of archives.
After this operation, 11.3 kB of additional disk space will be used.
Do you want to continue? [Y/n] 
Get:1 http://us-west1.gce.archive.ubuntu.com/ubuntu focal/universe amd64 python3-venv amd64 3.8.2-0ubuntu2 [1228 B]
Fetched 1228 B in 0s (17.2 kB/s)       
Selecting previously unselected package python3-venv.
(Reading database ... 197373 files and directories currently installed.)
Preparing to unpack .../python3-venv_3.8.2-0ubuntu2_amd64.deb ...
Unpacking python3-venv (3.8.2-0ubuntu2) ...
Setting up python3-venv (3.8.2-0ubuntu2) ...
Processing triggers for man-db (2.9.1-1) ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Create a python virtual environment
&lt;/h3&gt;

&lt;p&gt;Make a directory for keeping your notebooks, and navigate to it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/lab/notebooks
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$NB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$NB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a virtual environment using Python's &lt;code&gt;venv&lt;/code&gt; module, installed above. I'm following the usual practice of &lt;em&gt;putting the venv within the project directory&lt;/em&gt;, rather than my preferred practice of putting venvs in &lt;code&gt;~/lib/venv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now activate the venv for the current shell session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Jupyter and bash_kernel
&lt;/h2&gt;

&lt;p&gt;Now that the venv is activated, install Jupyter and the bash_kernel into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; pip jupyter bash_kernel
python &lt;span class="nt"&gt;-m&lt;/span&gt; bash_kernel.install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we can start Jupyter with the bash kernel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Jupyter notebook
&lt;/h2&gt;

&lt;p&gt;Launch the notebook, without automatically spawning a web browser, since that makes no sense on the remote host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jupyter notebook &lt;span class="nt"&gt;--no-browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jupyter will print status information to the terminal. Among it are details for connecting securely to the server to view and run notebooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
[I 2025-03-08 21:30:42.929 ServerApp] Serving notebooks from local directory: /home/mjl/lab/notebooks
[I 2025-03-08 21:30:42.929 ServerApp] Jupyter Server 2.14.2 is running at:
[I 2025-03-08 21:30:42.929 ServerApp] http://localhost:8888/tree?token=REDACTEDTOKEN
[I 2025-03-08 21:30:42.929 ServerApp]     http://127.0.0.1:8888/tree?token=REDACTEDTOKEN
[I 2025-03-08 21:30:42.929 ServerApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 2025-03-08 21:30:42.933 ServerApp]

    To access the server, open this file in a browser:
        file:///home/mjl/.local/share/jupyter/runtime/jpserver-7453-open.html
    Or copy and paste one of these URLs:
        http://localhost:8888/tree?token=REDACTEDTOKEN
        http://127.0.0.1:8888/tree?token=REDACTEDTOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that a fresh token is generated each time that Jupyter starts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tunnel to Jupyter and open in a local web browser
&lt;/h3&gt;

&lt;p&gt;Typically, the next step when running Jupyter on a remote host, is to "tunnel to the host" and then connect a local web browser through the tunnel to view the notebook server. The SSH command for that is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; localhost:8888:localhost:8888 username@remote_host
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that is connected, use your local web browser to view the URL printed by Jupyter above. If the tunnel held, you are in and have a web interface to Jupyter Notebook.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running bash in a notebook
&lt;/h3&gt;

&lt;p&gt;You can create notebooks on the remote host using the Bash kernel, and run bash commands in cells of the notebook, executing on the remote host!&lt;/p&gt;

&lt;p&gt;What's great about this is that the bash kernal maintains state, just like any other Jupyter kernel. So the environment persists between cells, as it is the same process executing each cell, unlike with Python-kernel cell-magics which fork a sub-process to execute the shell commands.&lt;/p&gt;

&lt;p&gt;So this makes the bash kernel for Jupyter ideal for exploring and documenting complicated Linux server setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect to the host from VSCode
&lt;/h2&gt;

&lt;p&gt;A nicer way to do this is with &lt;a href="https://code.visualstudio.com/docs/remote/ssh" rel="noopener noreferrer"&gt;VSCode Remote SSH&lt;/a&gt;. It has these advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automatically forward ports, so port &lt;code&gt;8888&lt;/code&gt; will be browsable locally, without the SSH command above&lt;/li&gt;
&lt;li&gt;Nicer / more powerful editing than Jupyer in a browser&lt;/li&gt;
&lt;li&gt;Open the notebook &lt;em&gt;directly&lt;/em&gt; and connect to the server running on the host&lt;/li&gt;
&lt;li&gt;Absolute filenames and directories that appear in the shell output can be clicked on and opened with VSCode&lt;/li&gt;
&lt;li&gt;View the Jupyter console output, and the notebook, together in the same window&lt;/li&gt;
&lt;li&gt;Navigate an outline of the notebook (like Jupyter Lab), browse files in the remote filesystem, run a remote terminal&lt;/li&gt;
&lt;li&gt;Other IDE features, like IntelliSense for shell aliases and functions &lt;em&gt;from within the Notebook&lt;/em&gt;!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you followed along the above with SSH in a normal Terminal application, but now want to try using VSCode, then exit the Jupyter server with ⌃-C (&lt;code&gt;Ctrl-C&lt;/code&gt;), and open a new VSCode Remote SSH connection to the host, from VSCode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
F1 &lt;code&gt;Remote-SSH: Connect Current Window to Host...&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(If the host is in your SSH configuration, it'll be selectable from the quick-pick)&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Python and Jupyter VSCode extensions
&lt;/h3&gt;

&lt;p&gt;You must install these VSCode extensions &lt;em&gt;in the Remote-SSH session&lt;/em&gt;. They are necessary for the integration to work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python" rel="noopener noreferrer"&gt;Python&lt;/a&gt; (Microsoft)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python" rel="noopener noreferrer"&gt;Jupyter&lt;/a&gt; (Microsoft)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Launch Jupyter inside VSCode
&lt;/h3&gt;

&lt;p&gt;Having installed the extensions, we can now open a new notebook in VSCode.&lt;/p&gt;

&lt;p&gt;Open the VSCode integrated terminal and change to the directory with the notebooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/lab/notebooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to activate the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start Jupyter again, like before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jupyter notebook &lt;span class="nt"&gt;--no-browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Jupyter server output will contain the new authentication token information, for this instance of Jupyter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making new notebooks and running cells
&lt;/h2&gt;

&lt;p&gt;With Jupyter running inside VSCode, there's an automatic port-forwarding for Jupyter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open a local web browser through the VSCode tunnel
&lt;/h3&gt;

&lt;p&gt;VSCode automatically detects listening TCP ports and tunnels them for us. We can connect our local web browser through the tunnel that VSCode provides, without need for special SSH parameters. Review the &lt;strong&gt;PORTS&lt;/strong&gt; panel to confirm the forwarded ports, but Jupyter usually opens port &lt;code&gt;8888&lt;/code&gt; by default:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://localhost:8888/?token=PUT_TOKEN_HERE" rel="noopener noreferrer"&gt;http://localhost:8888/?token=PUT_TOKEN_HERE&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use the connection details shown on the &lt;strong&gt;TERMINAL&lt;/strong&gt; panel for the token.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open a Notebook directly in VSCode
&lt;/h3&gt;

&lt;p&gt;You can create and interact with notebooks directly in VSCode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new buffer.&lt;/li&gt;
&lt;li&gt;Save the buffer with a &lt;code&gt;.ipynb&lt;/code&gt; filename extension. The file re-loads as a Notebook, in &lt;a href="https://code.visualstudio.com/docs/datascience/jupyter-notebooks" rel="noopener noreferrer"&gt;VSCode's Notebook editor&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Select the Bash kernel from the &lt;strong&gt;Select a kernel&lt;/strong&gt; button in the top-right.

&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;Jupyter kernel...&lt;/strong&gt; from the quick-pick list.&lt;/li&gt;
&lt;li&gt;Then choose &lt;strong&gt;Bash (venv/bin/python)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Add a code cell by pressing A and enter some bash code in it. Try &lt;code&gt;uname -a&lt;/code&gt; to convince yourself it is the remote host.&lt;/li&gt;

&lt;li&gt;Run the cell to test that it works, using the  ⇧-return keyboard shortcut.&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Enjoy editing and executing bash cells with the full features of the VSCode editor and any other extensions that you would like to add.&lt;/p&gt;

&lt;h1&gt;
  
  
  Side-note: Storage volume consumption by VSCode and Jupyter
&lt;/h1&gt;

&lt;p&gt;Do note that after installing all of this, there will be quite a lot of data in your &lt;code&gt;$HOME&lt;/code&gt; directory. The VSCode server components added 510MB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; ~/.vscode-server/&lt;span class="k"&gt;*&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;21M /home/mjl/.vscode-server/code-e54c774e0add60467559eb0d1e229c6452cf8447
60M /home/mjl/.vscode-server/data
195M    /home/mjl/.vscode-server/cli
234M    /home/mjl/.vscode-server/extensions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Breaking down the 234MB of extensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; ~/.vscode-server/extensions/&lt;span class="k"&gt;*&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8.0K    /home/mjl/.vscode-server/extensions/extensions.json
116K    /home/mjl/.vscode-server/extensions/ms-toolsai.jupyter-keymap-1.1.2
512K    /home/mjl/.vscode-server/extensions/ms-toolsai.vscode-jupyter-slideshow-0.1.6
556K    /home/mjl/.vscode-server/extensions/ms-toolsai.vscode-jupyter-cell-tags-0.1.9
21M /home/mjl/.vscode-server/extensions/ms-toolsai.jupyter-2025.1.0-linux-x64
25M /home/mjl/.vscode-server/extensions/ms-toolsai.jupyter-renderers-1.1.0
38M /home/mjl/.vscode-server/extensions/ms-python.debugpy-2025.4.0-linux-x64
48M /home/mjl/.vscode-server/extensions/ms-python.python-2025.2.0-linux-x64
103M    /home/mjl/.vscode-server/extensions/ms-python.vscode-pylance-2025.3.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Also the python virtual environment, and Jupyter itself, are pretty significant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; ~/lab/notebooks/venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;247M    /home/mjl/lab/notebooks/venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; ~/lab/notebooks/venv/share/&lt;span class="k"&gt;*&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12K /home/mjl/lab/notebooks/venv/share/applications
12K /home/mjl/lab/notebooks/venv/share/man
40K /home/mjl/lab/notebooks/venv/share/icons
2.2M    /home/mjl/lab/notebooks/venv/share/python-wheels
23M /home/mjl/lab/notebooks/venv/share/jupyter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Be mindful of this storage consumption!  Running Jupyter within VSCode on remote hosts is probably something only to be done in environments where you can afford this extra ¾ GB storage.&lt;/p&gt;

&lt;p&gt;It's also installed &lt;em&gt;per user&lt;/em&gt;, so a shared host — such as a university mainframe — will quickly balloon the &lt;code&gt;/home&lt;/code&gt; volume.&lt;/p&gt;

</description>
      <category>literate</category>
      <category>bash</category>
      <category>jupyter</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Saving and Restoring GCP instance snapshots</title>
      <dc:creator>Mike Lockhart</dc:creator>
      <pubDate>Mon, 10 Mar 2025 20:12:00 +0000</pubDate>
      <link>https://dev.to/sinewalker/saving-and-restoring-gcp-instance-snapshots-46fl</link>
      <guid>https://dev.to/sinewalker/saving-and-restoring-gcp-instance-snapshots-46fl</guid>
      <description>&lt;p&gt;This post lists the full steps for how to snapshot/restore a Google Cloud Platform (GCP) compute instance, illustrating a few things that might relieve some frustration about performing this "simple operation" in the Googleplex.&lt;/p&gt;

&lt;p&gt;Recently I was making a lot of changes to some GitLab virtual machines as I learnt about &lt;a href="https://gitlab.com/gitlab-com/support/support-training/-/issues/3944" rel="noopener noreferrer"&gt;setting up GitLab Geo&lt;/a&gt;, and often I would make mistakes and need to start over. This is very doable with virtual machines managed through infrastructure-as-code with Terraform and Ansible, as we do with the &lt;a href="https://gitlab.com/gitlab-org/gitlab-environment-toolkit" rel="noopener noreferrer"&gt;GitLab Environment Toolkit&lt;/a&gt;, but it does take about 25 minutes to spin up each new virtual machine and install GitLab.&lt;/p&gt;

&lt;p&gt;Twenty-five minutes is &lt;em&gt;amazing&lt;/em&gt; compared to doing it all by hand, but I felt it would be faster to use snapshots of the machine instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build new machines for Geo primary and secondary&lt;/li&gt;
&lt;li&gt;Snapshot them before making the tricky changes&lt;/li&gt;
&lt;li&gt;and then if one breaks, restore the snapshot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The virtual machines are GCP compute instances, so I needed to learn how to do this with the &lt;code&gt;gcloud compute&lt;/code&gt; command-line interface to GCP.&lt;/p&gt;




&lt;h2&gt;
  
  
  Snapshot a GCP compute instance
&lt;/h2&gt;

&lt;p&gt;In GCP, one manages the &lt;em&gt;disks&lt;/em&gt;, &lt;em&gt;snapshots&lt;/em&gt;, and &lt;em&gt;instances&lt;/em&gt; separately. To "snapshot and restore an instance", one really snapshots the disk(s) and then swaps in new disks created from the snapshots.&lt;/p&gt;

&lt;p&gt;There are 6 steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a snapshot from the instance's &lt;em&gt;disk&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;This can be done with the instance online, multiple times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute disks snapshot &lt;span class="se"&gt;\&lt;/span&gt;
  DISK_NAME &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--snapshot-names&lt;/span&gt; SNAPSHOT_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. To restore a snapshot, first create a &lt;em&gt;new disk&lt;/em&gt; from the snapshot
&lt;/h3&gt;

&lt;p&gt;Depending on the size of the disk, this might take a while, so it's good to do this only when you discover that you need to restore a snapshot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute disks create &lt;span class="se"&gt;\&lt;/span&gt;
  NEW_DISK_NAME &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--source-snapshot&lt;/span&gt; SNAPSHOT_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Stop the instance
&lt;/h3&gt;

&lt;p&gt;While the snapshot and new disk &lt;em&gt;can be made with the instance still running&lt;/em&gt;, you must stop the instance to swap disks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instances stop &lt;span class="se"&gt;\&lt;/span&gt;
  INSTANCE_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;em&gt;Detatch the current disk&lt;/em&gt; from the instance
&lt;/h3&gt;

&lt;p&gt;Instances can only have one boot disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instances detach-disk &lt;span class="se"&gt;\&lt;/span&gt;
  INSTANCE_NAME &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--disk&lt;/span&gt; DISK_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. &lt;em&gt;Attach the new disk&lt;/em&gt; to the instance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instances attach-disk &lt;span class="se"&gt;\&lt;/span&gt;
  INSTANCE_NAME &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--disk&lt;/span&gt; NEW_DISK_NAME &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--boot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Start the instance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instances start &lt;span class="se"&gt;\&lt;/span&gt;
  INSTANCE_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Consistency with multiple disks
&lt;/h2&gt;

&lt;p&gt;If the instance has multiple disks, then — to maintain consistency of data — &lt;em&gt;it may be necessary to stop the instance for multiple disk snapshots&lt;/em&gt;. That would depend on the application and the how the data are distributed over the disks. Also be mindful of which is the &lt;code&gt;--boot&lt;/code&gt; disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful snapshot and disk commands
&lt;/h2&gt;

&lt;p&gt;Some more commands are helpful in working with disks and snapshots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gcloud compute disks list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gcloud compute disks delete DISK_NAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gcloud compute snapshots list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gcloud compute snapshots delete SNAPSHOT_NAME&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why do this? It's too complicated!
&lt;/h2&gt;

&lt;p&gt;Once you have a snapshot, you can &lt;a href="https://cloud.google.com/compute/docs/disks/restore-snapshot#gcloud" rel="noopener noreferrer"&gt;create a new instance from it&lt;/a&gt;. But creating &lt;em&gt;new disks&lt;/em&gt; from snapshots and &lt;em&gt;swapping them in&lt;/em&gt; has the advantage that the compute instance itself is kept, with the same IP address, labels, and other attributes.&lt;/p&gt;

&lt;p&gt;If it were a spot instance, or you must not stop the old instance, then creating a new instance may be better.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;gcloud&lt;/code&gt; commands allow this separation of concern, so that you can do snapshot operations and create new disks and instances, without stopping the current instances. In a production environment that's the more common scenario: you can't always stop an instance. But it is still possible, and it's conceptually only 3 steps to do it.&lt;/p&gt;

</description>
      <category>howto</category>
      <category>google</category>
      <category>gcp</category>
      <category>virtualization</category>
    </item>
    <item>
      <title>Tech projects at start of 2023</title>
      <dc:creator>Mike Lockhart</dc:creator>
      <pubDate>Sun, 22 Jan 2023 21:35:52 +0000</pubDate>
      <link>https://dev.to/sinewalker/tech-projects-at-start-of-2023-5hhb</link>
      <guid>https://dev.to/sinewalker/tech-projects-at-start-of-2023-5hhb</guid>
      <description>&lt;p&gt;I was ruminating about what's on my wish-list for tech tasks that I'd like to complete this year, and then it struck me that there's a lot going on to update my blog with.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is my first blog post that I'm also posting to Dev.to. Socials can be here: I don't run a commenting system on my own web site.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;The stuff I've been working on lately, but not blogging about, is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Getting my &lt;a href="https://gitlab.com/milohax-net/radix/bootstrap/-/issues" rel="noopener noreferrer"&gt;bootstrap&lt;/a&gt; to work on macOS and openSUSE (testing in Tumbleweed) (works on Mac, openSUSE is 90% complete)&lt;/li&gt;
&lt;li&gt;Ansible playbook for &lt;a href="https://gitlab.com/milohax/workstation/" rel="noopener noreferrer"&gt;workstation&lt;/a&gt; setup (works for Mac! Now need to add a branch for SUSE)&lt;/li&gt;
&lt;li&gt;Figuring out how I want to &lt;a href="https://gitlab.com/milohax-net/radix/dotfiles/-/issues/4" rel="noopener noreferrer"&gt;install my dotfiles&lt;/a&gt; (I am leaning towards &lt;a href="https://www.atlassian.com/git/tutorials/dotfiles" rel="noopener noreferrer"&gt;a bare Git Repo&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Playing with &lt;a href="https://gitlab.com/milohax-net/radix/dotfiles/-/issues/3" rel="noopener noreferrer"&gt;tmux&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Switching to &lt;a href="https://hachyderm.io/@milohax" rel="noopener noreferrer"&gt;mastodon&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Getting ready for a short (15 minute) demo at the &lt;a href="https://www.meetup.com/gitlabanz/events/290508374/" rel="noopener noreferrer"&gt;next virtual GitLab Meetup&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm all over the place!&lt;/p&gt;

&lt;h2&gt;
  
  
  GitLab Meetup
&lt;/h2&gt;

&lt;p&gt;The thing that's most urgent at the moment is &lt;strong&gt;actually preparing for that Meetup&lt;/strong&gt;. It's a virtual meetup, happening at 12:30PM Melbourne time on Tuesday February 14&lt;sup&gt;th&lt;/sup&gt; (2023-02-14 01:30 UTC). My part is very small, and I'm going to be doing a short demo based upon &lt;a href="///tags/gitlab-profile.html"&gt;my GitLab Profile blog posts&lt;/a&gt;. The key here will be to keep it short and sweet. I do want to accomplish some key things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do it on a &lt;a href="https://gitlab.com/mlockhart/lab/-/issues/166" rel="noopener noreferrer"&gt;self-managed GitLab CE&lt;/a&gt; rather than gitlab.com (because the meetup seems to be talking about CI/CD in CE)&lt;/li&gt;
&lt;li&gt;Explain the &lt;a href="https://docs.gitlab.com/ee/user/profile/#add-details-to-your-profile-with-a-readme" rel="noopener noreferrer"&gt;custom profile project&lt;/a&gt; naming convention&lt;/li&gt;
&lt;li&gt;Show updating a git repo from within a CI job (and &lt;a href="https://gitlab.com/milohax/hax/-/blob/9910fed0dd38fd523f0f1a98fc6f9998326cf334/gitlab/git_clone_and_push.yml" rel="noopener noreferrer"&gt;link to my code for reference&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Quickly demonstrate &lt;a href="https://gitlab.com/milohax/hax/-/snippets/2220897" rel="noopener noreferrer"&gt;building and publishing&lt;/a&gt; a &lt;a href="https://gitlab.com/milohax/hax/container_registry/2563226" rel="noopener noreferrer"&gt;custom docker image&lt;/a&gt; with the tools needed, and &lt;a href="https://gitlab.com/milohax/hax/-/snippets/2220896" rel="noopener noreferrer"&gt;overcoming a common access problem&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Keep the actual mechanics of RSS parsing very simple: the point is to demo GitLab, not the RSS parsing, although the examples use it&lt;/li&gt;
&lt;li&gt;I also want to make sure that I'm following &lt;a href="https://about.gitlab.com/handbook/marketing/corporate-communications/speaking-resources/" rel="noopener noreferrer"&gt;GitLab's Handbook on public speeking&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a lot just in itself, actually! You can see that I've been busy documenting all this in issues and snippets. I &lt;em&gt;only&lt;/em&gt; need to pull it all together into something that can be quickly groked by someone else….&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching to Mastodon
&lt;/h2&gt;

&lt;p&gt;I've joined the recent exodus from the bird app. The latest round of layoffs included those who do the mediation of toxic tweets. That was the final straw for me, though I've had an on/off relationship for a few years now.  My new handle is &lt;a href="https://hachyderm.io/@milohax" rel="noopener noreferrer"&gt;@milohax@hachyderm.io&lt;/a&gt;. I've started tooting there and I feel about ten times more comfortable. I've started making connections around technical topics, which is that server's speciality. I also have an alt for my Australian interests: &lt;a href="https://mastodon.au/@milohax" rel="noopener noreferrer"&gt;@milohax@mastodon.au&lt;/a&gt;, though I'm not very active there yet. I'll see how it goes having an alt, I'm not sure if it's the right thing to do or not.&lt;/p&gt;

&lt;p&gt;So, all the links in this blog that went to the bird now go to Hachyderm, and from there to the rest of the fediverse. It's so much nicer and fixes a lot of the problems that have made centralised social media so toxic. As more of the people I follow make the same move, I'll need to visit those places less and less. I'm looking forward to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Booting up a new workstation
&lt;/h2&gt;

&lt;p&gt;My work upgraded my MacBook Pro to a new model with an M1-Max chip, and I got to keep the 2019 MacIntel MBP! It's very nice to have &lt;em&gt;my own Macintosh&lt;/em&gt; after 38 years, and about 25 years after doing my major dev project at UTas on a &lt;a href="https://everymac.com/systems/apple/mac_lc/specs/mac_lc_ii.html" rel="noopener noreferrer"&gt;Macintosh LC II&lt;/a&gt;. I do like the Mac, even though moving back to one in 2016 was an adjustment. While I miss using KDE as my daily driver, macOS does have some very nice creature comforts, just ones that I wouldn't have paid for myself. Now I don't need to, so I'm looking into configuration management.&lt;/p&gt;

&lt;p&gt;I also want to keep configurations for my Linux desktop, which runs KDE on openSUSE, the &lt;a href="https://milohax.net/#OpenSUSE" rel="noopener noreferrer"&gt;best Linux for devolopment work, IMO&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So to do this, I'm updating my &lt;a href="https://gitlab.com/milohax-net/radix/dotfiles" rel="noopener noreferrer"&gt;dotfiles&lt;/a&gt;, and writing a new &lt;a href="https://gitlab.com/milohax/workstation" rel="noopener noreferrer"&gt;ansible workstation playbook&lt;/a&gt; to install software packages for macOS and openSUSE. It's based off &lt;a href="https://github.com/geerlingguy/mac-dev-playbook" rel="noopener noreferrer"&gt;Jeff Geerling's mac-dev-playbook&lt;/a&gt;. To install the base requirements for Ansible, I'm updating a &lt;a href="https://gitlab.com/milohax-net/radix/bootstrap" rel="noopener noreferrer"&gt;bootstrap&lt;/a&gt; project, which also clones my &lt;a href="https://gitlab.com/milohax-net/radix/bashing" rel="noopener noreferrer"&gt;bash library&lt;/a&gt; and &lt;a href="https://gitlab.com/milohax-net/radix/fishing" rel="noopener noreferrer"&gt;fish library&lt;/a&gt;. That's a whole lot of things to pull together, but I'm working through it gradually. Some of this effort will translate to my work as well, as I'd like to put my dotfiles and bash library onto test servers using Ansible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tmux is really nice
&lt;/h2&gt;

&lt;p&gt;I have read about &lt;code&gt;tmux&lt;/code&gt; in the past, and it looked really good, but I never had opportunity to use it at my last job. For this job though, I am routinely running up temporary High Availability GitLab deployments to test customer scenarios. A modest-sized &lt;a href="https://docs.gitlab.com/ee/administration/reference_architectures/3k_users.html" rel="noopener noreferrer"&gt;3k reference deployment&lt;/a&gt; installs with 32 hosts using our &lt;a href="https://gitlab.com/gitlab-org/gitlab-environment-toolkit" rel="noopener noreferrer"&gt;GitLab Environment Toolkit&lt;/a&gt; (the same size as a banking application I once supported!) and this is ideal for &lt;code&gt;tmux&lt;/code&gt;'s windows and panes, and synchronized typing. At the moment it's a new hobby, but it will give me some material to write about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blogging
&lt;/h2&gt;

&lt;p&gt;Last year I only made one post. I'd like to make more, though I'm aware that I've made promisses before &lt;code&gt;;-)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;My wish is to be semi regular, and to federate my posts on &lt;a href="https://dev.to/sinewalker"&gt;Dev.to&lt;/a&gt; and the fediverse. It least I know that I have a lot to write!&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Share your most embarrassing shell pipeline</title>
      <dc:creator>Mike Lockhart</dc:creator>
      <pubDate>Sun, 15 Sep 2019 00:43:24 +0000</pubDate>
      <link>https://dev.to/sinewalker/share-your-most-embarrassing-shell-pipeline-356e</link>
      <guid>https://dev.to/sinewalker/share-your-most-embarrassing-shell-pipeline-356e</guid>
      <description>&lt;p&gt;Sometimes airing your code smells can be cathartic, and motivate you to fix it.&lt;/p&gt;

&lt;p&gt;Here's possibly the most embarrassing shell pipeline I currently have in my Bash dotfiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;            &lt;span class="se"&gt;\g&lt;/span&gt;rep &lt;span class="nt"&gt;-Rn&lt;/span&gt; &lt;span class="s2"&gt;"alias &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;="&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOTFILES&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/source &lt;span class="se"&gt;\&lt;/span&gt;
                | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;: &lt;span class="s1"&gt;'{print "⍺:\t" $3 "\t--&amp;gt; " $1 ":" $2}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                    | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=//g"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, I'm combining &lt;em&gt;all&lt;/em&gt; the filters, &lt;code&gt;grep&lt;/code&gt;,&lt;code&gt;awk&lt;/code&gt;, and &lt;code&gt;sed&lt;/code&gt; to print out an information of where a shell alias is defined in my files. It's part of my &lt;code&gt;describe&lt;/code&gt; function for handling aliases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ describe gdfs
⍺:      alias 'cpulimit -l 2 -p $(pgrep -f "crash_handler_token=")&amp;amp;'    --&amp;gt; /Users/mjl/.dotfiles/source/40_osx.sh:28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm &lt;em&gt;sure&lt;/em&gt; all of this can actually be done in &lt;code&gt;awk&lt;/code&gt; alone (especially the &lt;code&gt;sed&lt;/code&gt; part), but I haven't figured a way to make it so.&lt;/p&gt;

</description>
      <category>awk</category>
      <category>sed</category>
      <category>grep</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
