• Setting Emacs Theme Based on Ambient Light

    I sit next to a window at work. On sunny days it’s easier to see a light editor theme, and when the sky is dark, a dark theme is easier on my eyes. So I decided to try to have my MacBook automatically switch the Emacs theme based on readings from the ambient light sensor.

    Demonstration of the theme switching in action

    There are two parts to this solution: a command-line executable to read data from the sensor, and then a small elisp function to do the theme switching.

    The program below is from StackOverflow and slightly modified. It gets the AppleLMUController IO service, then, when the service is ready, prints the light sensor data to stdout and exits.

    // lmutracker.mm
    //
    // clang -o lmutracker lmutracker.mm -framework IOKit -framework CoreFoundation
    
    #include <mach/mach.h>
    #import <IOKit/IOKitLib.h>
    #import <CoreFoundation/CoreFoundation.h>
    
    static double updateInterval = 0.1;
    static io_connect_t dataPort = 0;
    
    void updateTimerCallBack(CFRunLoopTimerRef timer, void *info) {
      kern_return_t kr;
      uint32_t outputs = 2;
      uint64_t values[outputs];
    
      kr = IOConnectCallMethod(dataPort, 0, nil, 0, nil, 0, values, &outputs, nil, 0);
      if (kr == KERN_SUCCESS) {
        printf("%8lld", values[0]);
        exit(0);
      }
    
      if (kr == kIOReturnBusy) {
        return;
      }
    
      mach_error("I/O Kit error:", kr);
      exit(kr);
    }
    
    int main(void) {
      kern_return_t kr;
      io_service_t serviceObject;
      CFRunLoopTimerRef updateTimer;
    
      serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController"));
      if (!serviceObject) {
        fprintf(stderr, "failed to find ambient light sensors\n");
        exit(1);
      }
    
      kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &dataPort);
      IOObjectRelease(serviceObject);
      if (kr != KERN_SUCCESS) {
        mach_error("IOServiceOpen:", kr);
        exit(kr);
      }
    
      setbuf(stdout, NULL);
    
      updateTimer = CFRunLoopTimerCreate(kCFAllocatorDefault,
                      CFAbsoluteTimeGetCurrent() + updateInterval, updateInterval,
                      0, 0, updateTimerCallBack, NULL);
      CFRunLoopAddTimer(CFRunLoopGetCurrent(), updateTimer, kCFRunLoopDefaultMode);
      CFRunLoopRun();
    
      exit(0);
    }
    

    The accompanying elisp code will invoke that executable on a timer and change the theme based on the light reading.

    (setq current-theme "dark")
    (defconst light-theme 'majapahit-light)
    (defconst dark-theme 'majapahit-dark)
    
    ;; will apply a dark theme if the room is dark, and a light theme if the room is
    ;; bright
    (defun change-theme-for-lighting ()
      (let* ((current-light-sensor-reading
              (string-to-number
               (shell-command-to-string "./lmutracker"))))
        (if (< current-light-sensor-reading 100000)
            (when (not (string-equal current-theme "dark"))
              (load-theme dark-theme 1)
              (setq current-theme "dark"))
          (when (not (string-equal current-theme "light"))
            (load-theme light-theme 1)
            (setq current-theme "light")))))
    
    ;; probably want to run this less frequently than every second
    (run-with-timer 0 1 #'change-theme-for-lighting)
    

    Edit 4/7/2019: Read a Russian translation of this post here.


  • Creating a new blog post in Emacs

    Now that my blog is based on markdown text files, some new tooling options have opened!

    • entire blog is under source control (done)
    • make targets for common actions (e.g. create new post, deploy, serve dev version) (done)
    • git hooks for publishing (need to think about this more)
    • blogging via emacs! (done!)

    I just wrote this function to create a new posts file and open a buffer for editing it. In fact, it’s how I’m editing this post right now!

    (defun new-blog-post ()
      (interactive)
      (let ((post-title (read-string "Enter new post title: ")))
        (let* ((posts-dir "/ssh:vps:~/projects/blog/_posts/")
               (clean-title (replace-regexp-in-string
                             "[^[:alpha:][:digit:]_-]"
                             ""
                             (s-replace " " "-" (downcase post-title))))
               (new-post-filename (concat
                                   (format-time-string "%Y-%m-%d")
                                   "-"
                                   clean-title
                                   ".md"))
               (frontmatter-template "---\nlayout: post\ntitle: {title}\ndate: {date}\n---\n\n")
               (frontmatter (s-replace "{date}"
                                       (format-time-string "%Y-%m-%d %H:%m %z")
                                       (s-replace "{title}"
                                                  post-title
                                                  frontmatter-template)))
               (new-post-file (expand-file-name new-post-filename posts-dir)))
          (if (file-exists-p new-post-file)
              (message "A post with that name already exists.")
            (write-region frontmatter nil new-post-file)
            (find-file new-post-file)))))
    

    This was also the first elisp function I wrote :)


  • under construction

    I was having some headaches with my existing shared hosting provider, so I decided to move this site to a VPS. I also didn’t want to run a LAMP stack, so I’ve moved this blog from WordPress to Jekyll. Please pardon the appearance as I find time to iron things out.


  • How to set up free SSL on shared-hosting with Let&#8217;s Encrypt

    I just updated this domain to use HTTPS with Let’s Encrypt as a certificate authority. Presently this site is on a shared-hosting provider and I had to generate a cert manually and then upload it. Here are instructions for doing that.

    Note: some shared-hosting providers may offer a way to automatically generate and install a Let’s Encrypt (or other CA) certificate directly through the cPanel. I’d recommend doing that if it’s an option :)

    First, download and install certbot. On a separate computer (i.e., not the website host), run certbot to generate a certificate:

    brew install certbot
    mkdir ~/letstencrypt && cd ~/letstencrypt/
    certbot --config-dir . --work-dir . --logs-dir . certonly --manual
    

    After displaying some prompts, certbot will produce a challenge string and ask you to upload a file to your host containing that content (using the http challenge). This is to prove control of the website.

    On the host, create the file as instructed. E.g. copy the challenge text, then:

    pbpaste > challengefile
    ssh myhost 'mkdir -p ~/public_html/.well-know/acme-challenge/'
    scp challengefile myhost:~/public_html/.well-known/acme-challenge/rPs-CyPusl...
    

    Then, confirm you’ve uploaded the file and complete the certbot setup to create the certificate. There will be a live directory containing the generated certificate and secret.

    live
    └── my-website.com
        ├── README
        ├── cert.pem -> ../../archive/my-website.com/cert1.pem
        ├── chain.pem -> ../../archive/my-website.com/chain1.pem
        ├── fullchain.pem -> ../../archive/my-website.com/fullchain1.pem
        └── privkey.pem -> ../../archive/my-website.com/privkey1.pem
    

    Copy the contents of fullchain.pem and paste them into the certificate text box of your cPanel’s SSL configuration settings or upload the certificate file directly.

    Finally, install the certificate and upload privkey.pem.

    Once the process is complete the challenge file can be removed from the server. You should now be able to access your domain over https.


  • Assuming an IAM role from an EC2 instance with its own assumed IAM role

    In AWS IAM’s authentication infrastructure, it’s possible for one IAM role to assume another. This is useful if, for example, a service application runs as an assumed role on EC2, but then wishes to assume another role.

    We wanted one of our applications to be able to get temporary credentials for a role via the AWS Security Token Service (STS).

    In our application (Clojure) code, this looked something like:

    (defn- get-temporary-assumee-role-creds []
      (let [sts-client (AWSSecurityTokenServiceClient.)
            assume-role-req (-> (AssumeRoleRequest.)
                                (.withRoleArn "arn:aws:iam::111111111111:role/assumee")
                                (.withRoleSessionName "assumer-service"))
            assume-role-result (.assumeRole sts-client assume-role-req)]
        (.getCredentials assume-role-result)))
    

    This code is running as role assumer and wants to assume IAM role assumee.

    The assumer role must have an attached policy giving it the ability to assume the assumee role:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "",
                "Effect": "Allow",
                "Action": "sts:AssumeRole",
                "Resource": "arn:aws:iam::111111111111:role/assumee"
            }
        ]
    }
    

    And, importantly, the assumee role must have a trust policy like:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "",
          "Effect": "Allow",
          "Principal": {
            "AWS": "arn:aws:iam::111111111111:role/assumer"
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }
    

    The above trust policy is for the assumee role and explicitly grants the assumer role the ability to assume the assumee role (whew).

    Without the roles and policies properly configured, it isn’t possible to assume the role. An example error:

    assumer-box$ aws sts assume-role --role-arn arn:aws:iam::111111111111:role/assumee --role-session-name sess
    
    A client error (AccessDenied) occurred when calling the AssumeRole operation: User: arn:aws:sts::111111111111:assumed-role/assumer/i-06b6226fe698e565e is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::111111111111:role/assumee