{"id":380,"date":"2026-04-19T05:16:48","date_gmt":"2026-04-19T05:16:48","guid":{"rendered":"https:\/\/beginnerprojects.com\/cms\/?p=380"},"modified":"2026-04-24T23:49:08","modified_gmt":"2026-04-24T23:49:08","slug":"hardening-the-workflow-one-click-encryption-in-mx-linux-xfce","status":"publish","type":"post","link":"https:\/\/beginnerprojects.com\/cms\/hardening-the-workflow-one-click-encryption-in-mx-linux-xfce\/","title":{"rendered":"Hardening the Workflow: One-Click Encryption in MX Linux XFCE"},"content":{"rendered":"\n<p>As a creator, my backup drives are essentially my external brain. They hold project archives, sensitive configurations, and private data that should not be left unprotected.<\/p>\n\n\n\n<p>For those of us in the Linux ecosystem, securing this data is straightforward because GPG (GNU Privacy Guard) is usually baked right into the OS. GPG is the industry standard for encrypting and signing data, providing a level of security that is both robust and open-source.<\/p>\n\n\n\n<p><strong>However, there is a friction point: the terminal.<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image alignleft size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"210\" height=\"485\" src=\"https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/Thunar-right-click-menu-encryption.webp\" alt=\"thunar right click menu encryption\" class=\"wp-image-383\" srcset=\"https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/Thunar-right-click-menu-encryption.webp 210w, https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/Thunar-right-click-menu-encryption-130x300.webp 130w\" sizes=\"auto, (max-width: 210px) 100vw, 210px\" \/><\/figure>\n\n\n\n<p>I&#8217;m comfortable with the command line, but when I&#8217;ve already got VSCodium and the Continue extension firing on all cylinders, the last thing I want on my monitor is another terminal window. Remembering GPG flags is a distraction I don&#8217;t need; I want to secure my sensitive info and get right back to the code.<\/p>\n\n\n\n<p>To solve this, I built a bridge between the power of GPG and the convenience of my GUI. I added custom actions to Thunar\u2014the file manager that comes with XFCE and my distribution of choice, MX Linux. Now, I can encrypt, decrypt, or permanently destroy a file with a simple right-click.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Architecture<\/h3>\n\n\n\n<p><a href=\"https:\/\/beginnerprojects.com\/cms\/my-2026-linux-setup-why-i-chose-xfce-over-gnome\/\" data-type=\"post\" data-id=\"313\">I keep my system lean<\/a>, so I started by creating a dedicated Scripts directory in my home folder (~\/Scripts). This keeps my automation separate from my project files. Inside that directory, I wrote three Python wrappers to handle the heavy lifting:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>encrypt_file.py<\/strong>: Wraps GPG to encrypt a file using a password.<\/li>\n\n\n\n<li><strong>decrypt_file.py<\/strong>: Handles the decryption process.<\/li>\n\n\n\n<li><strong>shred_file.py<\/strong>: Ensures a file is unrecoverable.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">encrypt_file.py<\/h3>\n\n\n\n<pre class=\"wp-block-code has-palette-color-11-background-color has-background\"><code>#!\/usr\/bin\/env python3\nimport os\nimport sys\nimport subprocess\n\ndef encrypt_file(path):\n    # Reject directories\n    if os.path.isdir(path):\n        print(f\"Skipping directory: {path}\")\n        return\n\n    # Reject missing files\n    if not os.path.isfile(path):\n        print(f\"Error: File does not exist: {path}\")\n        return\n\n    encrypted_path = path + \".gpg\"\n\n    try:\n        # Call GPG without capturing output so it can prompt for passphrase\n        subprocess.run(\n            &#91;\"gpg\", \"--cipher-algo\", \"AES256\", \"-c\", path],\n            check=True\n        )\n\n        # Confirm encryption succeeded\n        if os.path.exists(encrypted_path):\n            os.remove(path)\n            print(f\"Encrypted: {os.path.basename(path)}\")\n        else:\n            print(\"Error: GPG did not create the encrypted file\")\n\n    except subprocess.CalledProcessError:\n        print(\"Encryption failed (GPG error)\")\n    except Exception as e:\n        print(f\"Unexpected error: {e}\")\n\nif __name__ == \"__main__\":\n    if len(sys.argv) &lt; 2:\n        print(\"Usage: encrypt.py &lt;file&gt;\")\n        sys.exit(1)\n\n    encrypt_file(sys.argv&#91;1])<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">decrypt_file.py<\/h3>\n\n\n\n<pre class=\"wp-block-code has-palette-color-11-background-color has-background\"><code>#!\/usr\/bin\/env python3\nimport sys\nimport subprocess\nimport os\n\nLOG_FILE = os.path.expanduser(\"~\/decrypt_debug.log\")\n\ndef log_error(msg):\n    try:\n        with open(LOG_FILE, \"a\") as f:\n            f.write(msg + \"\\n\")\n    except Exception:\n        pass\n\ndef decrypt_file(file_path):\n    # Must be a .gpg file\n    if not file_path.endswith(\".gpg\"):\n        subprocess.run(&#91;'notify-send', 'Error', 'This is not a .gpg file!'])\n        return\n\n    if os.path.isdir(file_path):\n        subprocess.run(&#91;'notify-send', 'Error', 'Cannot decrypt a directory!'])\n        return\n\n    if not os.path.isfile(file_path):\n        subprocess.run(&#91;'notify-send', 'Error', f'File does not exist: {file_path}'])\n        log_error(f\"File does not exist: {file_path}\")\n        return\n\n    output_path = file_path&#91;:-4]  # strip .gpg\n\n    try:\n        # Explicitly write decrypted data to output_path\n        result = subprocess.run(\n            &#91;'gpg', '--decrypt', '-o', output_path, file_path],\n            check=False\n        )\n\n        if result.returncode == 0 and os.path.exists(output_path):\n            msg = \"Successfully decrypted \" + os.path.basename(output_path)\n            subprocess.run(&#91;'notify-send', 'Decryption Complete', msg])\n        else:\n            log_error(f\"Decryption failed. Return code: {result.returncode}\")\n            subprocess.run(&#91;'notify-send', 'Decryption Error', 'Decryption failed. Check log.'])\n\n    except Exception as e:\n        log_error(\"Unexpected error: \" + str(e))\n        subprocess.run(&#91;'notify-send', 'Error', str(e)])\n\nif __name__ == \"__main__\":\n    if len(sys.argv) &gt; 1:\n        decrypt_file(sys.argv&#91;1])\n    else:\n        log_error(\"No file path provided to script\")\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">shred_file.py<\/h3>\n\n\n\n<pre class=\"wp-block-code has-palette-color-11-background-color has-background\"><code>#!\/usr\/bin\/env python3\nimport sys\nimport subprocess\nimport os\nimport time\n\nLOG_FILE = os.path.expanduser(\"~\/shred_debug.log\")\n\ndef log_error(msg):\n    try:\n        with open(LOG_FILE, \"a\") as f:\n            f.write(msg + \"\\n\")\n    except:\n        pass\n\ndef shred_file(file_path):\n    if not os.path.isfile(file_path):\n        subprocess.run(&#91;'notify-send', 'Shred Error', 'File does not exist.'])\n        log_error(f\"File does not exist: {file_path}\")\n        return\n\n    try:\n        # Run shred normally so errors appear in stderr\n        result = subprocess.run(\n            &#91;'shred', '-u', file_path],\n            stderr=subprocess.PIPE,\n            text=True\n        )\n\n        if result.returncode == 0 and not os.path.exists(file_path):\n            subprocess.run(&#91;'notify-send', 'File Shredded',\n                            f\"Securely shredded {os.path.basename(file_path)}\"])\n        else:\n            log_error(f\"Shred failed: {result.stderr}\")\n            subprocess.run(&#91;'notify-send', 'Shred Error',\n                            'File still exists or shred failed.'])\n\n    except Exception as e:\n        log_error(f\"Unexpected error: {e}\")\n        subprocess.run(&#91;'notify-send', 'Error', str(e)])\n\nif __name__ == \"__main__\":\n    if len(sys.argv) &gt; 1:\n        shred_file(sys.argv&#91;1])\n    else:\n        log_error(\"No file path provided to script\")<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Integrating with Thunar<\/h3>\n\n\n\n<p>The scripts are the engine, but the <strong>Thunar &#8220;Custom Actions&#8221;<\/strong> are the steering wheel.<\/p>\n\n\n\n<p>I configured Thunar to recognize these scripts as valid actions for files. By mapping the Python scripts to the right-click menu, I removed the need to ever touch the terminal for these specific tasks.<\/p>\n\n\n\n<p><strong>How I set it up<\/strong><br>Thunar &gt; Edit &gt; Configure Custom Actions&#8230; (add new actoin by clicking the + button)<br><\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"400\" height=\"300\" src=\"https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/thunar-custom-action.webp\" alt=\"thunar custom actions\" class=\"wp-image-381\" srcset=\"https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/thunar-custom-action.webp 400w, https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/thunar-custom-action-300x225.webp 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><figcaption class=\"wp-element-caption\">Enter Name and Command (path to the pyton script)<\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"396\" height=\"250\" src=\"https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/appearance-conditions.webp\" alt=\"thunar custom actions appearance conditions\" class=\"wp-image-382\" srcset=\"https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/appearance-conditions.webp 396w, https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/appearance-conditions-300x189.webp 300w\" sizes=\"auto, (max-width: 396px) 100vw, 396px\" \/><figcaption class=\"wp-element-caption\">Here is my selection<\/figcaption><\/figure>\n<\/div>\n<\/div>\n\n\n\n<p>I simply pointed Thunar to the path of each script in my ~\/Scripts folder and defined the parameters so that the selected file is passed as an argument to the script.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Result<\/h3>\n\n\n\n<p>My workflow is now significantly more streamlined. When I move sensitive content to a backup drive or need to wipe a temporary credential file, I don&#8217;t leave my file manager. I locate the file in Thunar, right-click, and choose the required action.<\/p>\n\n\n\n<p>The security is still provided by the industrial-strength GPG backend, but the interface is now invisible. It\u2019s a small optimization, but it\u2019s exactly how I prefer to build my environment: <a href=\"https:\/\/beginnerprojects.com\/cms\/category\/guides\/\" data-type=\"category\" data-id=\"1\">powerful tools, zero friction<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p><em>If you&#8217;re looking for a step-by-step walkthrough on the implementation, I recommend feeding these scripts into an LLM and asking it to guide you through the setup for your specific distro. That said, if you&#8217;ve found a leaner way to handle file security or have questions about the logic, leave a comment below. Let&#8217;s discuss.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>As a creator, my backup drives are essentially my external brain. They hold project archives, sensitive configurations, and private data that should not be left unprotected. For those of us in the Linux ecosystem, securing this data is straightforward because GPG (GNU Privacy Guard) is usually baked right into the OS. GPG is the industry [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_ecmd_meta_description":"Stop using the terminal for basic security. See how I added one-click Encrypt, Decrypt, and Shred actions to the Thunar file manager in MX Linux.","footnotes":""},"categories":[1],"tags":[],"class_list":["post-380","post","type-post","status-publish","format-standard","hentry","category-guides"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/posts\/380","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/comments?post=380"}],"version-history":[{"count":3,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/posts\/380\/revisions"}],"predecessor-version":[{"id":466,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/posts\/380\/revisions\/466"}],"wp:attachment":[{"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/media?parent=380"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/categories?post=380"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/tags?post=380"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}