💾 Archived View for her.esy.fun › posts › 0003-project-el › index.gmi captured on 2021-11-30 at 20:18:30. Gemini links have been rewritten to link to archived content

View Raw

More Information

-=-=-=-=-=-=-

Autoload Script by project

subtitle: fast, secure, easy autoload

author: Yann Esposito

email: yann@esposito.host

gpg

date: [2019-08-18 Sun]

keywords: programming blog org-mode

description: A script I use to load safely an eLISP file when entering a new project directory.

______

/tl;dr/: A script that use projectile and GPG to securely load

an emacs lisp script when opening a new project.

Check the => #solution section to get the code.

______

Problem

When providing a repository containing only org files of my blog.

I also wanted to provide everything necessary for users to be able to publish my

website.

Emacs, org-publish mostly assume you should put all those details in a

centralised place in your =~/.emacs.d/.init.el= file.

The main principle is quite simple.

1. When opening a new file in a project, check for the presence of a

=project.el= and =project.el.sig= file at the root directory of the

project.

2. Check the file was signed with by a trusted fingerprint.

3. Load the file.

Other solutions I found on the internet asked you each time you enter in a

project if you trust the file or not.

This was both quite annoying and insecure as it is kind of easy to type 'y'

instead of 'n' and to load 3rd party script.

Note that checking who signed a file with an external signature is not as

straightforward as it should be:

(defun auto-load-project/get-sign-key (file)
  "Return the fingerprint of they key that signed FILE.

To sign a file you should used

`gpg --local-user my@email --output project.el.sig --detach-sign project.el`"
  (string-trim-right
   (shell-command-to-string
    (concat
     (format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file)
     "|grep VALIDSIG"
     "|awk '{print $3}'"))))

GPG provide localized output by default which are therefore hard to use in

script (for grep for example).

We use =projectile= to detect the project-root and when we are in a new project.

Unfortunately the =projectile-after-switch-project-hooks= doesn't work as I

expected.

So I use the hooks =find-file-hook= and =dired-mode-hook= to try to load the

file.

In order not to load the code each time, I need to keep a local state of project

already loaded.

So now, each time I modify the =project.el= I sign it with the following

command line:

gpg --local-user my@email --output project.el.sig --detach-sign project.el

Solution

The project is hosted here: https://gitlab.esy.fun/yogsototh/auto-load-project-el

You can setup the emacs package in spacemacs with:

  ;; ...
  dotspacemacs-additional-packages
  '((auto-load-project :location
                       (recipe
                        :fetcher git
                        :url "https://gitlab.esy.fun/yogsototh/auto-load-project-el"
                        :files ("auto-load-project.el"))))
  ;; ...
  (defun dotspacemacs/user-config ()
    ;; ...
    (require 'auto-load-project)
    (setq auto-load-project/trusted-gpg-key-fingerprints
          '("0000000000000000000000000000000000000000"
            "1111111111111111111111111111111111111111"
            "2222222222222222222222222222222222222222"
            )))
  ;; ...

The full current code should be easy to follow if you have basic notions

of eLISP:

(require 'projectile)

(defvar auto-load-project/trusted-gpg-key-fingerprints
  '()
  "The list of GPG fingerprint you trust when decrypting a gpg file.
You can retrieve the fingerprints of your own private keys
with: `gpg --list-secret-keys' (take care of removing the
spaces when copy/pasting here)")

(defun auto-load-project/get-sign-key (file)
  "Return the fingerprint of they key that signed FILE.

To sign a file you should used

`gpg --local-user my@email --output project.el.sig --detach-sign project.el`"
  (string-trim-right
   (shell-command-to-string
    (concat
     (format "gpg --status-fd 1 --verify %s.sig %s 2>/dev/null " file file)
     "|grep VALIDSIG"
     "|awk '{print $3}'"))))

(defun auto-load-project/trusted-gpg-origin-p (file)
  "Return non-nil if the FILE is encrypted with a trusted key."
  (member (auto-load-project/get-sign-key file)
          auto-load-project/trusted-gpg-key-fingerprints))

(defconst auto-load-project/project-file "project.el"
  "Project configuration file name.")

(defvar auto-load-project/loaded-projects (list)
  "Projects that have been loaded by `auto-load-project/load'.")

(defun auto-load-project/load ()
  "Loads the `auto-load-project/project-file' for a project.
This is run once the project is loaded signifying project setup."
  (interactive)
  (when (projectile-project-p)
    (lexical-let* ((current-project-root (projectile-project-root))
                   (project-init-file (expand-file-name auto-load-project/project-file current-project-root))
                   (project-sign-file (concat project-init-file ".sig")))
      (when (and (not (member current-project-root auto-load-project/loaded-projects))
                 (file-exists-p project-init-file)
                 (file-exists-p project-sign-file)
                 (auto-load-project/trusted-gpg-origin-p project-init-file))
        (message "Loading project init file for %s" (projectile-project-name))
        (condition-case ex
            (progn (load project-init-file)
                   (add-to-list 'auto-load-project/loaded-projects current-project-root)
                   (message "%s loaded successfully" project-init-file))
          ('error
           (message
            "There was an error loading %s: %s"
            project-init-file
            (error-message-string ex))))))))

(add-hook 'find-file-hook #'auto-load-project/load t)
(add-hook 'dired-mode-hook #'auto-load-project/load t)

(provide 'auto-load-project)

Home

Feed

Slides

About

code

bookmarks

notes