Browse Source

Hugo commit

master
Michael Zhang 8 months ago
parent
commit
552b6b3892
Signed by: michael GPG Key ID: BDA47A31A3C8EE6B
  1. 6
      .drone.yml
  2. 0
      .garbage
  3. 4
      .gitignore
  4. 6
      archetypes/default.md
  5. 15
      assets/sass/_content.scss
  6. 2
      assets/sass/_syntax.scss
  7. 2
      assets/sass/main.scss
  8. 24
      config.toml
  9. 13
      content/_index.md
  10. 23
      content/about/_index.md
  11. 639
      content/enterprise/2020-02-11-prototype/helloworld.js
  12. BIN
      content/enterprise/2020-02-11-prototype/helloworld.wasm
  13. 101
      content/enterprise/2020-02-11-prototype/index.md
  14. 100
      content/enterprise/2020-02-17-syntax-update.md
  15. 7
      content/enterprise/_index.md
  16. 9
      content/pages/_index.md
  17. 16
      content/pages/about.md
  18. 11
      content/pages/projects.md
  19. 0
      content/posts/2018-02-01-my-new-life-stack.md
  20. 0
      content/posts/2018-02-25-cleaning-up-your-shell.md
  21. 0
      content/posts/2018-04-23-fixing-tmux-colors.md
  22. 0
      content/posts/2018-05-28-web-apps.md
  23. 0
      content/posts/2018-10-26-twenty-years-of-rsa-attacks.md
  24. 0
      content/posts/2019-02-01-magic-forms-with-proc-macros.md
  25. 0
      content/posts/2019-03-04-server-analogy.md
  26. 0
      content/posts/2020-04-01-password-managers.md
  27. 0
      content/posts/2020-04-12-drawing-bezier-curves.md
  28. 6
      content/posts/_index.md
  29. 87
      content/setup/2018-10-18-weechat-relay.md
  30. BIN
      content/setup/2020-05-04-command-line-email-with-aerc/aerc-mail.jpg
  31. 195
      content/setup/2020-05-04-command-line-email-with-aerc/index.md
  32. 11
      content/setup/_index.md
  33. 43
      layouts/_default/baseof.html
  34. 5
      layouts/generic/list.html
  35. 7
      layouts/home.html
  36. 19
      layouts/partials/post-list.html
  37. 7
      layouts/posts/list.html
  38. 20
      layouts/posts/single.html
  39. 22
      sass/_graph.scss
  40. 56
      static/.well-known/keybase.txt
  41. 1
      static/.well-known/matrix/server
  42. 2
      static/pub.key
  43. 10
      templates/404.html
  44. 3
      templates/anchor-link.html
  45. 18
      templates/blog.html
  46. 36
      templates/layout.html
  47. 16
      templates/listing.html
  48. 59
      templates/macros/blog.html
  49. 12
      templates/macros/layout.html
  50. 12
      templates/macros/post.html
  51. 42
      templates/post.html
  52. 26
      templates/rss.xml
  53. 14
      templates/tags/list.html
  54. 17
      templates/tags/single.html

6
.drone.yml

@ -1,6 +0,0 @@
pipeline:
build:
image: j1mc/docker-zola
commands:
- zola build

0
.garbage

4
.gitignore

@ -1 +1,3 @@
public
/public
/old
/resources

6
archetypes/default.md

@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

15
sass/_content.scss → assets/sass/_content.scss

@ -73,13 +73,6 @@ blockquote {
}
}
pre {
padding: 5px;
overflow-x: auto;
font-family: $monofont;
font-size: 0.9em;
}
code {
font-size: 1.2em;
padding: 3px;
@ -87,3 +80,11 @@ code {
color: #cccccc;
border-radius: 5px;
}
pre > code {
display: block;
padding: 5px;
overflow-x: auto;
font-family: $monofont;
font-size: 0.9em;
}

2
sass/_syntax.scss → assets/sass/_syntax.scss

@ -67,4 +67,4 @@ pre.highlight { background-color: #444444; color: #cccccc }
.highlight .vc { color: #569cd6 } /* Name.Variable.Class */
.highlight .vg { color: #00cdcd } /* Name.Variable.Global */
.highlight .vi { color: #00cdcd } /* Name.Variable.Instance */
.highlight .il { color: #b5cea8 } /* Literal.Number.Integer.Long */
.highlight .il { color: #b5cea8 } /* Literal.Number.Integer.Long */

2
sass/main.scss → assets/sass/main.scss

@ -11,7 +11,6 @@ $monofont: "Roboto Mono", "Roboto Mono for Powerline", "Inconsolata", "Consolas"
$small-text-color: lighten($text-color, 15%);
$link-color: royalblue;
@import "content";
@import "graph";
}
@media (prefers-color-scheme: dark) {
@ -20,5 +19,4 @@ $monofont: "Roboto Mono", "Roboto Mono for Powerline", "Inconsolata", "Consolas"
$small-text-color: darken($text-color, 15%);
$link-color: lightskyblue;
@import "content";
@import "graph";
}

24
config.toml

@ -1,22 +1,4 @@
baseURL = "http://example.org/"
languageCode = "en-us"
title = "michael's blog"
base_url = "https://iptq.io"
compile_sass = true
highlight_code = true
generate_rss = true
taxonomies = [
{ name = "tags", rss = true }
]
[external_renderers]
dot = "set -e; echo -n '<p class=\"graphviz\">'; dot -Tsvg | tail -n +4; echo '</p>'"
[extra]
nav_links = [
{ url = "/", text = "home" },
{ url = "/pages/about", text = "about" },
{ url = "/pages/projects", text = "projects" },
{ url = "/setup", text = "setup" },
{ url = "/pages", text = "all pages" },
]
enableGitInfo = true

13
content/_index.md

@ -1,11 +1,8 @@
+++
title = "home"
template = "blog.html"
page_template = "post.html"
layout = "home"
insert_anchor_links = "left"
sort_by = "date"
[extra]
include_posts = true
[cascade]
type = "generic"
+++
hello

23
content/about/_index.md

@ -0,0 +1,23 @@
+++
title = "about"
+++
Hi there! I'm a software enthusiast who recently graduated with a Computer
Science degree from the University of Minnesota. I've been doing web
development for a long time and now I'm looking into security, programming
language development, and software development!
In an effort to rely on less services, I started doing a lot of self-hosting
and rewriting of software. Check out some of the projects I'm doing over on my
public [Gitea][2]!
If you want my resume, contact me through one of these means:
## contact
- Email: (I sign all my Git commits with this email)
- PGP Key: [hosted on Keybase][1]
- IRC: michael on acm.umn.edu
[1]: https://keybase.io/michaelz/pgp_keys.asc?fingerprint=925ecc02890d5cdae26180d4bda47a31a3c8ee6b
[2]: https://git.mzhang.io/explore

639
content/enterprise/2020-02-11-prototype/helloworld.js

@ -1,639 +0,0 @@
"use strict";
if( typeof Rust === "undefined" ) {
var Rust = {};
}
(function( root, factory ) {
if( typeof define === "function" && define.amd ) {
define( [], factory );
} else if( typeof module === "object" && module.exports ) {
module.exports = factory();
} else {
Rust.helloworld = factory();
}
}( this, function() {
return (function( module_factory ) {
var instance = module_factory();
if( typeof process === "object" && typeof process.versions === "object" && typeof process.versions.node === "string" ) {
var fs = require( "fs" );
var path = require( "path" );
var wasm_path = path.join( __dirname, "helloworld.wasm" );
var buffer = fs.readFileSync( wasm_path );
var mod = new WebAssembly.Module( buffer );
var wasm_instance = new WebAssembly.Instance( mod, instance.imports );
return instance.initialize( wasm_instance );
} else {
var file = fetch( "helloworld.wasm", {credentials: "same-origin"} );
var wasm_instance = ( typeof WebAssembly.instantiateStreaming === "function"
? WebAssembly.instantiateStreaming( file, instance.imports )
.then( function( result ) { return result.instance; } )
: file
.then( function( response ) { return response.arrayBuffer(); } )
.then( function( bytes ) { return WebAssembly.compile( bytes ); } )
.then( function( mod ) { return WebAssembly.instantiate( mod, instance.imports ) } ) );
return wasm_instance
.then( function( wasm_instance ) {
var exports = instance.initialize( wasm_instance );
console.log( "Finished loading Rust wasm module 'helloworld'" );
return exports;
})
.catch( function( error ) {
console.log( "Error loading Rust wasm module 'helloworld':", error );
throw error;
});
}
}( function() {
var Module = {};
Module.STDWEB_PRIVATE = {};
// This is based on code from Emscripten's preamble.js.
Module.STDWEB_PRIVATE.to_utf8 = function to_utf8( str, addr ) {
var HEAPU8 = Module.HEAPU8;
for( var i = 0; i < str.length; ++i ) {
// Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! So decode UTF16->UTF32->UTF8.
// See http://unicode.org/faq/utf_bom.html#utf16-3
// For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description and https://www.ietf.org/rfc/rfc2279.txt and https://tools.ietf.org/html/rfc3629
var u = str.charCodeAt( i ); // possibly a lead surrogate
if( u >= 0xD800 && u <= 0xDFFF ) {
u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt( ++i ) & 0x3FF);
}
if( u <= 0x7F ) {
HEAPU8[ addr++ ] = u;
} else if( u <= 0x7FF ) {
HEAPU8[ addr++ ] = 0xC0 | (u >> 6);
HEAPU8[ addr++ ] = 0x80 | (u & 63);
} else if( u <= 0xFFFF ) {
HEAPU8[ addr++ ] = 0xE0 | (u >> 12);
HEAPU8[ addr++ ] = 0x80 | ((u >> 6) & 63);
HEAPU8[ addr++ ] = 0x80 | (u & 63);
} else if( u <= 0x1FFFFF ) {
HEAPU8[ addr++ ] = 0xF0 | (u >> 18);
HEAPU8[ addr++ ] = 0x80 | ((u >> 12) & 63);
HEAPU8[ addr++ ] = 0x80 | ((u >> 6) & 63);
HEAPU8[ addr++ ] = 0x80 | (u & 63);
} else if( u <= 0x3FFFFFF ) {
HEAPU8[ addr++ ] = 0xF8 | (u >> 24);
HEAPU8[ addr++ ] = 0x80 | ((u >> 18) & 63);
HEAPU8[ addr++ ] = 0x80 | ((u >> 12) & 63);
HEAPU8[ addr++ ] = 0x80 | ((u >> 6) & 63);
HEAPU8[ addr++ ] = 0x80 | (u & 63);
} else {
HEAPU8[ addr++ ] = 0xFC | (u >> 30);
HEAPU8[ addr++ ] = 0x80 | ((u >> 24) & 63);
HEAPU8[ addr++ ] = 0x80 | ((u >> 18) & 63);
HEAPU8[ addr++ ] = 0x80 | ((u >> 12) & 63);
HEAPU8[ addr++ ] = 0x80 | ((u >> 6) & 63);
HEAPU8[ addr++ ] = 0x80 | (u & 63);
}
}
};
Module.STDWEB_PRIVATE.noop = function() {};
Module.STDWEB_PRIVATE.to_js = function to_js( address ) {
var kind = Module.HEAPU8[ address + 12 ];
if( kind === 0 ) {
return undefined;
} else if( kind === 1 ) {
return null;
} else if( kind === 2 ) {
return Module.HEAP32[ address / 4 ];
} else if( kind === 3 ) {
return Module.HEAPF64[ address / 8 ];
} else if( kind === 4 ) {
var pointer = Module.HEAPU32[ address / 4 ];
var length = Module.HEAPU32[ (address + 4) / 4 ];
return Module.STDWEB_PRIVATE.to_js_string( pointer, length );
} else if( kind === 5 ) {
return false;
} else if( kind === 6 ) {
return true;
} else if( kind === 7 ) {
var pointer = Module.STDWEB_PRIVATE.arena + Module.HEAPU32[ address / 4 ];
var length = Module.HEAPU32[ (address + 4) / 4 ];
var output = [];
for( var i = 0; i < length; ++i ) {
output.push( Module.STDWEB_PRIVATE.to_js( pointer + i * 16 ) );
}
return output;
} else if( kind === 8 ) {
var arena = Module.STDWEB_PRIVATE.arena;
var value_array_pointer = arena + Module.HEAPU32[ address / 4 ];
var length = Module.HEAPU32[ (address + 4) / 4 ];
var key_array_pointer = arena + Module.HEAPU32[ (address + 8) / 4 ];
var output = {};
for( var i = 0; i < length; ++i ) {
var key_pointer = Module.HEAPU32[ (key_array_pointer + i * 8) / 4 ];
var key_length = Module.HEAPU32[ (key_array_pointer + 4 + i * 8) / 4 ];
var key = Module.STDWEB_PRIVATE.to_js_string( key_pointer, key_length );
var value = Module.STDWEB_PRIVATE.to_js( value_array_pointer + i * 16 );
output[ key ] = value;
}
return output;
} else if( kind === 9 ) {
return Module.STDWEB_PRIVATE.acquire_js_reference( Module.HEAP32[ address / 4 ] );
} else if( kind === 10 || kind === 12 || kind === 13 ) {
var adapter_pointer = Module.HEAPU32[ address / 4 ];
var pointer = Module.HEAPU32[ (address + 4) / 4 ];
var deallocator_pointer = Module.HEAPU32[ (address + 8) / 4 ];
var num_ongoing_calls = 0;
var drop_queued = false;
var output = function() {
if( pointer === 0 || drop_queued === true ) {
if (kind === 10) {
throw new ReferenceError( "Already dropped Rust function called!" );
} else if (kind === 12) {
throw new ReferenceError( "Already dropped FnMut function called!" );
} else {
throw new ReferenceError( "Already called or dropped FnOnce function called!" );
}
}
var function_pointer = pointer;
if (kind === 13) {
output.drop = Module.STDWEB_PRIVATE.noop;
pointer = 0;
}
if (num_ongoing_calls !== 0) {
if (kind === 12 || kind === 13) {
throw new ReferenceError( "FnMut function called multiple times concurrently!" );
}
}
var args = Module.STDWEB_PRIVATE.alloc( 16 );
Module.STDWEB_PRIVATE.serialize_array( args, arguments );
try {
num_ongoing_calls += 1;
Module.STDWEB_PRIVATE.dyncall( "vii", adapter_pointer, [function_pointer, args] );
var result = Module.STDWEB_PRIVATE.tmp;
Module.STDWEB_PRIVATE.tmp = null;
} finally {
num_ongoing_calls -= 1;
}
if( drop_queued === true && num_ongoing_calls === 0 ) {
output.drop();
}
return result;
};
output.drop = function() {
if (num_ongoing_calls !== 0) {
drop_queued = true;
return;
}
output.drop = Module.STDWEB_PRIVATE.noop;
var function_pointer = pointer;
pointer = 0;
if (function_pointer != 0) {
Module.STDWEB_PRIVATE.dyncall( "vi", deallocator_pointer, [function_pointer] );
}
};
return output;
} else if( kind === 14 ) {
var pointer = Module.HEAPU32[ address / 4 ];
var length = Module.HEAPU32[ (address + 4) / 4 ];
var array_kind = Module.HEAPU32[ (address + 8) / 4 ];
var pointer_end = pointer + length;
switch( array_kind ) {
case 0:
return Module.HEAPU8.subarray( pointer, pointer_end );
case 1:
return Module.HEAP8.subarray( pointer, pointer_end );
case 2:
return Module.HEAPU16.subarray( pointer, pointer_end );
case 3:
return Module.HEAP16.subarray( pointer, pointer_end );
case 4:
return Module.HEAPU32.subarray( pointer, pointer_end );
case 5:
return Module.HEAP32.subarray( pointer, pointer_end );
case 6:
return Module.HEAPF32.subarray( pointer, pointer_end );
case 7:
return Module.HEAPF64.subarray( pointer, pointer_end );
}
} else if( kind === 15 ) {
return Module.STDWEB_PRIVATE.get_raw_value( Module.HEAPU32[ address / 4 ] );
}
};
Module.STDWEB_PRIVATE.serialize_object = function serialize_object( address, value ) {
var keys = Object.keys( value );
var length = keys.length;
var key_array_pointer = Module.STDWEB_PRIVATE.alloc( length * 8 );
var value_array_pointer = Module.STDWEB_PRIVATE.alloc( length * 16 );
Module.HEAPU8[ address + 12 ] = 8;
Module.HEAPU32[ address / 4 ] = value_array_pointer;
Module.HEAPU32[ (address + 4) / 4 ] = length;
Module.HEAPU32[ (address + 8) / 4 ] = key_array_pointer;
for( var i = 0; i < length; ++i ) {
var key = keys[ i ];
var key_address = key_array_pointer + i * 8;
Module.STDWEB_PRIVATE.to_utf8_string( key_address, key );
Module.STDWEB_PRIVATE.from_js( value_array_pointer + i * 16, value[ key ] );
}
};
Module.STDWEB_PRIVATE.serialize_array = function serialize_array( address, value ) {
var length = value.length;
var pointer = Module.STDWEB_PRIVATE.alloc( length * 16 );
Module.HEAPU8[ address + 12 ] = 7;
Module.HEAPU32[ address / 4 ] = pointer;
Module.HEAPU32[ (address + 4) / 4 ] = length;
for( var i = 0; i < length; ++i ) {
Module.STDWEB_PRIVATE.from_js( pointer + i * 16, value[ i ] );
}
};
// New browsers and recent Node
var cachedEncoder = ( typeof TextEncoder === "function"
? new TextEncoder( "utf-8" )
// Old Node (before v11)
: ( typeof util === "object" && util && typeof util.TextEncoder === "function"
? new util.TextEncoder( "utf-8" )
// Old browsers
: null ) );
if ( cachedEncoder != null ) {
Module.STDWEB_PRIVATE.to_utf8_string = function to_utf8_string( address, value ) {
var buffer = cachedEncoder.encode( value );
var length = buffer.length;
var pointer = 0;
if ( length > 0 ) {
pointer = Module.STDWEB_PRIVATE.alloc( length );
Module.HEAPU8.set( buffer, pointer );
}
Module.HEAPU32[ address / 4 ] = pointer;
Module.HEAPU32[ (address + 4) / 4 ] = length;
};
} else {
Module.STDWEB_PRIVATE.to_utf8_string = function to_utf8_string( address, value ) {
var length = Module.STDWEB_PRIVATE.utf8_len( value );
var pointer = 0;
if ( length > 0 ) {
pointer = Module.STDWEB_PRIVATE.alloc( length );
Module.STDWEB_PRIVATE.to_utf8( value, pointer );
}
Module.HEAPU32[ address / 4 ] = pointer;
Module.HEAPU32[ (address + 4) / 4 ] = length;
};
}
Module.STDWEB_PRIVATE.from_js = function from_js( address, value ) {
var kind = Object.prototype.toString.call( value );
if( kind === "[object String]" ) {
Module.HEAPU8[ address + 12 ] = 4;
Module.STDWEB_PRIVATE.to_utf8_string( address, value );
} else if( kind === "[object Number]" ) {
if( value === (value|0) ) {
Module.HEAPU8[ address + 12 ] = 2;
Module.HEAP32[ address / 4 ] = value;
} else {
Module.HEAPU8[ address + 12 ] = 3;
Module.HEAPF64[ address / 8 ] = value;
}
} else if( value === null ) {
Module.HEAPU8[ address + 12 ] = 1;
} else if( value === undefined ) {
Module.HEAPU8[ address + 12 ] = 0;
} else if( value === false ) {
Module.HEAPU8[ address + 12 ] = 5;
} else if( value === true ) {
Module.HEAPU8[ address + 12 ] = 6;
} else if( kind === "[object Symbol]" ) {
var id = Module.STDWEB_PRIVATE.register_raw_value( value );
Module.HEAPU8[ address + 12 ] = 15;
Module.HEAP32[ address / 4 ] = id;
} else {
var refid = Module.STDWEB_PRIVATE.acquire_rust_reference( value );
Module.HEAPU8[ address + 12 ] = 9;
Module.HEAP32[ address / 4 ] = refid;
}
};
// New browsers and recent Node
var cachedDecoder = ( typeof TextDecoder === "function"
? new TextDecoder( "utf-8" )
// Old Node (before v11)
: ( typeof util === "object" && util && typeof util.TextDecoder === "function"
? new util.TextDecoder( "utf-8" )
// Old browsers
: null ) );
if ( cachedDecoder != null ) {
Module.STDWEB_PRIVATE.to_js_string = function to_js_string( index, length ) {
return cachedDecoder.decode( Module.HEAPU8.subarray( index, index + length ) );
};
} else {
// This is ported from Rust's stdlib; it's faster than
// the string conversion from Emscripten.
Module.STDWEB_PRIVATE.to_js_string = function to_js_string( index, length ) {
var HEAPU8 = Module.HEAPU8;
index = index|0;
length = length|0;
var end = (index|0) + (length|0);
var output = "";
while( index < end ) {
var x = HEAPU8[ index++ ];
if( x < 128 ) {
output += String.fromCharCode( x );
continue;
}
var init = (x & (0x7F >> 2));
var y = 0;
if( index < end ) {
y = HEAPU8[ index++ ];
}
var ch = (init << 6) | (y & 63);
if( x >= 0xE0 ) {
var z = 0;
if( index < end ) {
z = HEAPU8[ index++ ];
}
var y_z = ((y & 63) << 6) | (z & 63);
ch = init << 12 | y_z;
if( x >= 0xF0 ) {
var w = 0;
if( index < end ) {
w = HEAPU8[ index++ ];
}
ch = (init & 7) << 18 | ((y_z << 6) | (w & 63));
output += String.fromCharCode( 0xD7C0 + (ch >> 10) );
ch = 0xDC00 + (ch & 0x3FF);
}
}
output += String.fromCharCode( ch );
continue;
}
return output;
};
}
Module.STDWEB_PRIVATE.id_to_ref_map = {};
Module.STDWEB_PRIVATE.id_to_refcount_map = {};
Module.STDWEB_PRIVATE.ref_to_id_map = new WeakMap();
// Not all types can be stored in a WeakMap
Module.STDWEB_PRIVATE.ref_to_id_map_fallback = new Map();
Module.STDWEB_PRIVATE.last_refid = 1;
Module.STDWEB_PRIVATE.id_to_raw_value_map = {};
Module.STDWEB_PRIVATE.last_raw_value_id = 1;
Module.STDWEB_PRIVATE.acquire_rust_reference = function( reference ) {
if( reference === undefined || reference === null ) {
return 0;
}
var id_to_refcount_map = Module.STDWEB_PRIVATE.id_to_refcount_map;
var id_to_ref_map = Module.STDWEB_PRIVATE.id_to_ref_map;
var ref_to_id_map = Module.STDWEB_PRIVATE.ref_to_id_map;
var ref_to_id_map_fallback = Module.STDWEB_PRIVATE.ref_to_id_map_fallback;
var refid = ref_to_id_map.get( reference );
if( refid === undefined ) {
refid = ref_to_id_map_fallback.get( reference );
}
if( refid === undefined ) {
refid = Module.STDWEB_PRIVATE.last_refid++;
try {
ref_to_id_map.set( reference, refid );
} catch (e) {
ref_to_id_map_fallback.set( reference, refid );
}
}
if( refid in id_to_ref_map ) {
id_to_refcount_map[ refid ]++;
} else {
id_to_ref_map[ refid ] = reference;
id_to_refcount_map[ refid ] = 1;
}
return refid;
};
Module.STDWEB_PRIVATE.acquire_js_reference = function( refid ) {
return Module.STDWEB_PRIVATE.id_to_ref_map[ refid ];
};
Module.STDWEB_PRIVATE.increment_refcount = function( refid ) {
Module.STDWEB_PRIVATE.id_to_refcount_map[ refid ]++;
};
Module.STDWEB_PRIVATE.decrement_refcount = function( refid ) {
var id_to_refcount_map = Module.STDWEB_PRIVATE.id_to_refcount_map;
if( 0 == --id_to_refcount_map[ refid ] ) {
var id_to_ref_map = Module.STDWEB_PRIVATE.id_to_ref_map;
var ref_to_id_map_fallback = Module.STDWEB_PRIVATE.ref_to_id_map_fallback;
var reference = id_to_ref_map[ refid ];
delete id_to_ref_map[ refid ];
delete id_to_refcount_map[ refid ];
ref_to_id_map_fallback.delete(reference);
}
};
Module.STDWEB_PRIVATE.register_raw_value = function( value ) {
var id = Module.STDWEB_PRIVATE.last_raw_value_id++;
Module.STDWEB_PRIVATE.id_to_raw_value_map[ id ] = value;
return id;
};
Module.STDWEB_PRIVATE.unregister_raw_value = function( id ) {
delete Module.STDWEB_PRIVATE.id_to_raw_value_map[ id ];
};
Module.STDWEB_PRIVATE.get_raw_value = function( id ) {
return Module.STDWEB_PRIVATE.id_to_raw_value_map[ id ];
};
Module.STDWEB_PRIVATE.alloc = function alloc( size ) {
return Module.web_malloc( size );
};
Module.STDWEB_PRIVATE.dyncall = function( signature, ptr, args ) {
return Module.web_table.get( ptr ).apply( null, args );
};
// This is based on code from Emscripten's preamble.js.
Module.STDWEB_PRIVATE.utf8_len = function utf8_len( str ) {
var len = 0;
for( var i = 0; i < str.length; ++i ) {
// Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! So decode UTF16->UTF32->UTF8.
// See http://unicode.org/faq/utf_bom.html#utf16-3
var u = str.charCodeAt( i ); // possibly a lead surrogate
if( u >= 0xD800 && u <= 0xDFFF ) {
u = 0x10000 + ((u & 0x3FF) << 10) | (str.charCodeAt( ++i ) & 0x3FF);
}
if( u <= 0x7F ) {
++len;
} else if( u <= 0x7FF ) {
len += 2;
} else if( u <= 0xFFFF ) {
len += 3;
} else if( u <= 0x1FFFFF ) {
len += 4;
} else if( u <= 0x3FFFFFF ) {
len += 5;
} else {
len += 6;
}
}
return len;
};
Module.STDWEB_PRIVATE.prepare_any_arg = function( value ) {
var arg = Module.STDWEB_PRIVATE.alloc( 16 );
Module.STDWEB_PRIVATE.from_js( arg, value );
return arg;
};
Module.STDWEB_PRIVATE.acquire_tmp = function( dummy ) {
var value = Module.STDWEB_PRIVATE.tmp;
Module.STDWEB_PRIVATE.tmp = null;
return value;
};
var HEAP8 = null;
var HEAP16 = null;
var HEAP32 = null;
var HEAPU8 = null;
var HEAPU16 = null;
var HEAPU32 = null;
var HEAPF32 = null;
var HEAPF64 = null;
Object.defineProperty( Module, 'exports', { value: {} } );
function __web_on_grow() {
var buffer = Module.instance.exports.memory.buffer;
HEAP8 = new Int8Array( buffer );
HEAP16 = new Int16Array( buffer );
HEAP32 = new Int32Array( buffer );
HEAPU8 = new Uint8Array( buffer );
HEAPU16 = new Uint16Array( buffer );
HEAPU32 = new Uint32Array( buffer );
HEAPF32 = new Float32Array( buffer );
HEAPF64 = new Float64Array( buffer );
Module.HEAP8 = HEAP8;
Module.HEAP16 = HEAP16;
Module.HEAP32 = HEAP32;
Module.HEAPU8 = HEAPU8;
Module.HEAPU16 = HEAPU16;
Module.HEAPU32 = HEAPU32;
Module.HEAPF32 = HEAPF32;
Module.HEAPF64 = HEAPF64;
}
return {
imports: {
env: {
"__cargo_web_snippet_09675c7ed2827e045dc760aeac3d286437cfbe5e": function($0, $1, $2, $3) {
$1 = Module.STDWEB_PRIVATE.to_js($1);$2 = Module.STDWEB_PRIVATE.to_js($2);$3 = Module.STDWEB_PRIVATE.to_js($3);Module.STDWEB_PRIVATE.from_js($0, (function(){try{return{value:function(){return($1).setAttribute(($2),($3));}(),success:true};}catch(error){return{error:error,success:false};}})());
},
"__cargo_web_snippet_0e54fd9c163fcf648ce0a395fde4500fd167a40b": function($0) {
var r = Module.STDWEB_PRIVATE.acquire_js_reference( $0 );return (r instanceof DOMException) && (r.name === "InvalidCharacterError");
},
"__cargo_web_snippet_1edaec034bdcb0a749c6d5de76c29f6371afb5a0": function($0) {
var o = Module.STDWEB_PRIVATE.acquire_js_reference( $0 );return (o instanceof Event && o.type === "input");
},
"__cargo_web_snippet_2908dbb08792df5e699e324eec3e29fd6a57c2c9": function($0) {
var o = Module.STDWEB_PRIVATE.acquire_js_reference( $0 );return (o instanceof HTMLInputElement);
},
"__cargo_web_snippet_3c5e83d16a83fc7147ec91e2506438012952f55a": function($0) {
var o = Module.STDWEB_PRIVATE.acquire_js_reference( $0 );return (o instanceof Element);
},
"__cargo_web_snippet_614a3dd2adb7e9eac4a0ec6e59d37f87e0521c3b": function($0, $1) {
$1 = Module.STDWEB_PRIVATE.to_js($1);Module.STDWEB_PRIVATE.from_js($0, (function(){return($1).error;})());
},
"__cargo_web_snippet_6fcce0aae651e2d748e085ff1f800f87625ff8c8": function($0) {
Module.STDWEB_PRIVATE.from_js($0, (function(){return document;})());
},
"__cargo_web_snippet_72fc447820458c720c68d0d8e078ede631edd723": function($0, $1, $2) {
console.error( 'Panic location:', Module.STDWEB_PRIVATE.to_js_string( $0, $1 ) + ':' + $2 );
},
"__cargo_web_snippet_80d6d56760c65e49b7be8b6b01c1ea861b046bf0": function($0) {
Module.STDWEB_PRIVATE.decrement_refcount( $0 );
},
"__cargo_web_snippet_91749aeb589cd0f9b17cbc01b2872ba709817982": function($0, $1, $2) {
$1 = Module.STDWEB_PRIVATE.to_js($1);$2 = Module.STDWEB_PRIVATE.to_js($2);Module.STDWEB_PRIVATE.from_js($0, (function(){try{return{value:function(){return($1).createElement(($2));}(),success:true};}catch(error){return{error:error,success:false};}})());
},
"__cargo_web_snippet_97495987af1720d8a9a923fa4683a7b683e3acd6": function($0, $1) {
console.error( 'Panic error message:', Module.STDWEB_PRIVATE.to_js_string( $0, $1 ) );
},
"__cargo_web_snippet_99c4eefdc8d4cc724135163b8c8665a1f3de99e4": function($0, $1, $2, $3) {
$1 = Module.STDWEB_PRIVATE.to_js($1);$2 = Module.STDWEB_PRIVATE.to_js($2);$3 = Module.STDWEB_PRIVATE.to_js($3);Module.STDWEB_PRIVATE.from_js($0, (function(){var listener=($1);($2).addEventListener(($3),listener);return listener;})());
},
"__cargo_web_snippet_9f22d4ca7bc938409787341b7db181f8dd41e6df": function($0) {
Module.STDWEB_PRIVATE.increment_refcount( $0 );
},
"__cargo_web_snippet_ab05f53189dacccf2d365ad26daa407d4f7abea9": function($0, $1) {
$1 = Module.STDWEB_PRIVATE.to_js($1);Module.STDWEB_PRIVATE.from_js($0, (function(){return($1).value;})());
},
"__cargo_web_snippet_afafe9a462a05084fec65cacc7d6598e145ff3e3": function($0, $1, $2) {
$1 = Module.STDWEB_PRIVATE.to_js($1);$2 = Module.STDWEB_PRIVATE.to_js($2);Module.STDWEB_PRIVATE.from_js($0, (function(){return($1).createTextNode(($2));})());
},
"__cargo_web_snippet_b06dde4acf09433b5190a4b001259fe5d4abcbc2": function($0, $1) {
$1 = Module.STDWEB_PRIVATE.to_js($1);Module.STDWEB_PRIVATE.from_js($0, (function(){return($1).success;})());
},
"__cargo_web_snippet_d5e30f74cb752784e06bd97a37b1f89b6c3433a7": function($0, $1, $2) {
$1 = Module.STDWEB_PRIVATE.to_js($1);$2 = Module.STDWEB_PRIVATE.to_js($2);Module.STDWEB_PRIVATE.from_js($0, (function(){return($1).getElementById(($2));})());
},
"__cargo_web_snippet_dc2fd915bd92f9e9c6a3bd15174f1414eee3dbaf": function() {
console.error( 'Encountered a panic!' );
},
"__cargo_web_snippet_e741b9d9071097746386b2c2ec044a2bc73e688c": function($0, $1) {
$0 = Module.STDWEB_PRIVATE.to_js($0);$1 = Module.STDWEB_PRIVATE.to_js($1);($0).appendChild(($1));
},
"__cargo_web_snippet_e9638d6405ab65f78daf4a5af9c9de14ecf1e2ec": function($0) {
$0 = Module.STDWEB_PRIVATE.to_js($0);Module.STDWEB_PRIVATE.unregister_raw_value(($0));
},
"__cargo_web_snippet_f765b15a1a1b5cd266e922e6fca98dd570f17edc": function($0, $1) {
$0 = Module.STDWEB_PRIVATE.to_js($0);$1 = Module.STDWEB_PRIVATE.to_js($1);($0).textContent=($1);
},
"__cargo_web_snippet_ff5103e6cc179d13b4c7a785bdce2708fd559fc0": function($0) {
Module.STDWEB_PRIVATE.tmp = Module.STDWEB_PRIVATE.to_js( $0 );
},
"__web_on_grow": __web_on_grow
}
},
initialize: function( instance ) {
Object.defineProperty( Module, 'instance', { value: instance } );
Object.defineProperty( Module, 'web_malloc', { value: Module.instance.exports.__web_malloc } );
Object.defineProperty( Module, 'web_free', { value: Module.instance.exports.__web_free } );
Object.defineProperty( Module, 'web_table', { value: Module.instance.exports.__indirect_function_table } );
__web_on_grow();
Module.instance.exports.main();
return Module.exports;
}
};
}
));
}));

BIN
content/enterprise/2020-02-11-prototype/helloworld.wasm

101
content/enterprise/2020-02-11-prototype/index.md

@ -1,101 +0,0 @@
+++
title = "enterprise: a new ui framework"
date = 2020-02-11
template = "post.html"
[taxonomies]
tags = ["computers", "web-dev"]
+++
This past weekend, while on my trip to Minneapolis, I completed a very early prototype of "enterprise", a new UI framework I've been kind of envisioning over the past couple of weeks. While the UI framework is mainly targeted at web apps, the hope is that with a bit more effort, native UIs can be produced with almost no changes to existing applications. Before I begin to describe how it works, I'd like to acknowledge [Nathan Ringo][1] for his massively helpful feedback in both the brainstorming and the implementation process.
<!-- more -->
## Goals of the project
This project was born out of many frustrations with existing web frameworks. This is kind of the combination of several projects I wanted to tackle; since it's such a long-term thing I'm going to document a bit of what I want to achieve so I can stay on track. The high-level goals of the project are:
* **Complete separation of business logic from UI.** Theoretically, one could completely retarget the application to a completely different platform (mobile, web, native, something new that will pop up in 5 years), without changing any of the core logic. It does this by introducing [declarative][4]-style [DSL][2]s that are completely architecture-independent.
* **Maximally static component relationships.** Like [Svelte][3], I'm aiming to resolve as many relationships between elements as possible during compile-time, to avoid having to maintain a full virtual DOM at runtime.
With that, let's dive into the code!
## Demo: Initial Prototype
The prototype for experimenting is a simple "Hello, world" application. If you've looked at any web framework before, this is probably one of the simplest examples of bindings: type something into a box and watch as the text magically populates with whatever you wrote in the box. If you're using a WASM-compatible browser with JavaScript enabled, you should be able to try out the demo right here:
<div id="app"></div>
<script type="text/javascript" src="helloworld.js"></script>
OK, you say, but I could implement this in 3 lines of JavaScript.
```js
inputEl.addEventListener("change", () => {
spanEl.innerText = inputEl.value;
});
```
Sure, this works, but it doesn't scale. If you try to write a page full of these kind of bindings directly using JavaScript, you're either going to start running into bugs or building up a pile of unmaintainable spaghetti code. How does enterprise represent this? Well, the enterprise DSL has no concrete syntax yet, but if it did, it would look something like this:
```
model {
name: String,
}
view {
<TextBox bind:value="name" />
Hello, {name}!
}
```
This looks a lot closer to {React, Vue, Svelte, component structure}-ish code. The idea now is that the "compiler", as I've come to call it, reads the entire specification of the code and creates a sort of dependency graph of actions. For clarity, let's assign some IDs first:
* Let the `TextBox`'s value attribute be `view_value`.
* Let the `{name}` code segment in "Hello, name" be `view_name`.
* Let the model's name field be `model_name`.
Now we can model this as:
```dot
digraph "dependency graph" {
graph[bgcolor="transparent", class="default-coloring"];
rankdir="LR";
"view_value" -> "model_name"
"model_name" -> "view_name"
}
```
The arrows in this graph indicate a dependency where changes to `view_value` propagates down the graph until everything else is changed. For the initial prototype, the data structure passed through this graph is simply a string, but with encoding magic and additional specifications, we can add rich text later. What this means the compiler then generates code that looks something like (but not exactly):
```rs
fn create_view_value(model: &mut Model) -> INode {
let el = (...);
el.add_event_listener(|evt: InputListener| {
let new_value = el.get_attribute("value");
model.name = new_value;
view_name.set_text(new_value);
});
el
}
```
There's some complications involving the exact model representation in memory as well as how web attributes are accessed that makes the real code a bit different, but from this example you should be able to see that our "compiler" generated real code that matches our specification above.
The full code for this can be found [here][5].
## Future
Obviously not everyone's application is as simple as a linear dependency graph of simple string values. In fact, I even cheated a bit to get this prototype to function; here's some of the shortcuts I took:
* I mostly hardcoded web elements in instead of making platform-independent abstractions.
* I hardcoded the actual specification for the app myself since the DSL doesn't have a real syntax yet.
* All data are string types.
I'll be working on this some more in the coming weeks, so I'll try to keep updates posted here a bit more frequently. Next time, I'll be looking into implementing the TodoMVC example. Until then, thanks for reading!
[1]: https://remexre.xyz
[2]: https://en.wikipedia.org/wiki/Domain-specific_language
[3]: https://svelte.dev
[4]: https://en.wikipedia.org/wiki/Declarative_programming
[5]: https://git.iptq.io/michael/enterprise

100
content/enterprise/2020-02-17-syntax-update.md

@ -1,100 +0,0 @@
+++
title = "enterprise: syntax update"
date = 2020-02-17
template = "post.html"
[taxonomies]
tags = ["computers", "web-dev"]
+++
[Enterprise][1]'s frontend DSL just got a syntax! Although the major functionality hasn't really changed, I threw out the ugly verbose AST-construction syntax for a hand-rolled recursive-descent-ish parser.
<!-- more -->
The rehashed "Hello, world" example looks a bit like this:
```
component HelloWorld {
model {
name: String = "hello",
}
view {
<input bind:value="name" />
"Hello, " {name} "!"
}
}
```
This compiles using `cargo-web` into a working version of the last post's prototype. You'll notice that quoted literals are used to represent text rather than just typing it out directly like in XML. This is because I'm actually borrowing Rust syntax and parsing it a bit differently. If I had bare text, then everything you put would have to follow Rust's lexical rules; additionally, data about spacing would be a lot more complicated (and unstable!) to retrieve.
I could possibly have thrown the whole thing into a parser-generator, using Rust's `proc-macro::TokenTree` as tokens, but TokenTree actually gives you blocks (eg. `()` `{}` `[]`) for free, so I can parse expressions like `{name}` incredibly easily.
Syntax isn't the only thing that's changed since the last update: I've also revamped how builds work.
New Build Method
----------------
I'm switching to a build method that's rather unconventional. The original approach looked something like this.
```dot
digraph "dependency graph" {
graph[bgcolor="transparent", class="default-coloring"];
rankdir="LR";
"Component DSL" -> "AST" [label = "Parsing"];
"AST" -> "Dependency Graph" [label = "Graph traversal"];
}
```
Problem here is, when we want code to be modular, the graph traversal approach is going to need information about _all_ modules that are imported in order to
be able to produce a flat set of instructions in the final result. If I make a library for a component (say, `enterprise-router`), what should its crate's contents be?
> **Tangent**: Here's where I'm going to distract myself a little and put this into a more big-picture perspective. Ultimately, the ideal manifestation of an architecture/business-logic separation would be a DSL that completely hides all implementation of its internals.
>
> That's a pretty far-out goal, so I'm building enterprise incrementally. Sadly, large parts of the language will still rely on the language in which this framework is implemented, Rust. This means that the underlying implementation of features such as modules and async will be relying on the Rust language having these features. However, note that in the long term, a separate DSL for business logic will be planned.
So what's the solution here? Instead of visiting your component node by node when your component is defined, all the framework is going to do is parse your definition and store the AST of your component as-is. I chose here to serialize ASTs as JSON data and dump it into a static string that will be bundled into your crate.
Then, in your `build.rs` file, you'll call something like `enterprise_compiler::build(App)`, where `App` is the name of the static string containing the JSON data of the description of your app. This will actually perform the analysis process, calculating the graph of update dependencies, as well as generating the code that will go into a Rust module that you can include into your code.
Your `build.rs` file might look something like this:
```rs
#[macro_use]
extern crate enterprise_macros;
component! {
component HelloWorld {
model {
name: String = "hello",
}
view {
<input bind:value="name" />
"Hello, " {name} "!"
}
}
}
fn main() {
enterprise_compiler::process("helloworld", HelloWorld);
}
```
This will create a string called `HelloWorld` for the HelloWorld component, and then analyze and generate the final application code for it into a file called `helloworld.rs` that you can `mod` into your application. The advantage to this approach is that external modules can just rely on Rust's crate system, since we're just fetching strings out of other crates.
Source code: [here][3].
Next Steps
----------
As mentioned in my previous post, I'm still working on implementing [TodoMVC][2], a simple Todo application that should flesh out some more of the reactive functionalities of the framework. This should solidify some more of the questions regarding interactions between data model and DOM.
I'll also try to abstract more of the system away so it's less dependent on stdweb's implementation. This means adding a notion of "backend", where different backends may have different implementations of a particular component.
[1]: @/enterprise/2020-02-11-prototype/index.md
[2]: http://todomvc.com/
[3]: https://git.iptq.io/michael/enterprise/src/commit/1453885ed2c3a5159431bb41398b9b8bea4d49f5

7
content/enterprise/_index.md

@ -1,7 +0,0 @@
+++
template = "blog.html"
insert_anchor_links = "left"
[extra]
include_posts = true
+++

9
content/pages/_index.md

@ -1,9 +0,0 @@
+++
title = "all pages"
template = "listing.html"
page_template = "post.html"
insert_anchor_links = "left"
[extra]
include_posts = false
+++

16
content/pages/about.md

@ -1,16 +0,0 @@
+++
title = "about me"
+++
Hi there! I'm a software developer at Epic Systems in Verona, Wisconsin, and I recently graduated with a Computer Science degree from the University of Minnesota. I've got a wide variety of interests under this field. I've been doing web development for a long time and now I'm looking into security, programming language development, and software development!
In an effort to rely on less services, I started doing a lot of self-hosting and rewriting of software. Check out some of the projects I'm doing over on my public [Gitea](https://git.iptq.io)!
If you want my resume, contact me through one of these means:
## contact
- Discord: **iptq#8440**
- Email: (I sign all my Git commits with this email)
- PGP Key: [hosted on Keybase][1]
[1]: https://keybase.io/michaelz/pgp_keys.asc?fingerprint=925ecc02890d5cdae26180d4bda47a31a3c8ee6b

11
content/pages/projects.md

@ -1,11 +0,0 @@
+++
title = "projects"
+++
- **[garbage](https://github.com/iptq/garbage)**: A command-line tool for interfacing with the FreeDesktop trashcan spec.
- **[leanshot](https://github.com/iptq/leanshot)**: Linux screen capturing tool that freezes the screen before selecting.
Other projects can be found:
- [Github](https://github.com/iptq)
- [Gitea](https://git.iptq.io/explore)

0
content/2018-02-01-my-new-life-stack.md → content/posts/2018-02-01-my-new-life-stack.md

0
content/2018-02-25-cleaning-up-your-shell.md → content/posts/2018-02-25-cleaning-up-your-shell.md

0
content/2018-04-23-fixing-tmux-colors.md → content/posts/2018-04-23-fixing-tmux-colors.md

0
content/2018-05-28-web-apps.md → content/posts/2018-05-28-web-apps.md

0
content/2018-10-26-twenty-years-of-rsa-attacks.md → content/posts/2018-10-26-twenty-years-of-rsa-attacks.md

0
content/2019-02-01-magic-forms-with-proc-macros.md → content/posts/2019-02-01-magic-forms-with-proc-macros.md

0
content/2019-03-04-server-analogy.md → content/posts/2019-03-04-server-analogy.md

0
content/2020-04-01-password-managers.md → content/posts/2020-04-01-password-managers.md

0
content/2020-04-12-drawing-bezier-curves.md → content/posts/2020-04-12-drawing-bezier-curves.md

6
content/posts/_index.md

@ -0,0 +1,6 @@
+++
title = "home"
[cascade]
type = "posts"
+++

87
content/setup/2018-10-18-weechat-relay.md

@ -1,87 +0,0 @@
+++
title = "setting up irc with weechat"
date = 2018-10-18
[taxonomies]
tags = ["computers", "irc", "setup", "things-that-are-good"]
+++
I've just recently discovered that weechat has a "relay" mode, which means it can act as a relay server to other clients (for example, my phone). If I leave an instance of weechat running on, say, my server that's always running, it can act as a bouncer and my phone can receive notifications for highlights as well.
The android app I'm using is called [Weechat-Android][2]. On my laptop I'm using [Glowing Bear][5].
## step 1: tmux
To achieve this setup, first I install [tmux][1], which separates the terminal from the session. This means I can leave the weechat instance running in the background and detach my current session from it. The command for this is:
```bash
$ tmux new-session -s weechat
```
where the `-s` option just names the tmux session so it's not assigned some number.
## step 2: add relay
Now add a relay through weechat:
```
/relay add <name> <port>
```
where name is
```
[ipv4.][ipv6.][ssl.]name
ipv4: force use of IPv4
ipv6: force use of IPv6
ssl: enable SSL
```
according to the [documentation][3].
## step 2.5: ssl
I'm using SSL on my relay endpoint, and I'd recommend anyone else to use it to. You could follow what the documentation says and generate a self-signed certificate, but getting a trusted certificate with [LetsEncrypt][4] is so easy there's almost no excuse not to do it.
To start, install certbot, which is LetsEncrypt's handy bot that does everything for you. Once you're ready, run:
```bash
$ sudo certbot certonly <domain>
```
We want the `certonly` option because by default, certbot will try to install it into an existing HTTP server, but we're not using it for HTTP. This command should dump some files into `/etc/letsencrypt/live/<domain>`.
Finally, just concatenate the important files, `privkey.pem` and `fullchain.pem` in that order, into `~/.weechat/ssl/relay.pem` (you can change that path with `/set relay.network.ssl_cert_key`). The file should look like:
```
-----BEGIN PRIVATE KEY-----
...data...
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
...data...
-----END CERTIFICATE-----
```
If your private key file starts with `BEGIN CERTIFICATE`, just change that to `BEGIN PRIVATE KEY` (change the END one too) and it should be fine.
## step 3: set password
Since weechat 1.6, the option to not use a password has been removed. So in order for clients to be able to connect to the server, you must set one using:
```
/set relay.network.password <password>
```
The password should appear in asterisks in the weechat prompt box.
## step 4: connect
This depends on your setup, but you must make sure that your setup is reachable from the outside. Make sure the port that you chose for the relay is accessible through firewalls.
That's it! If you're also using the android app to connect, just type in your host's address and password and you should be all good to go.
[1]: https://wiki.archlinux.org/index.php/Tmux
[2]: https://github.com/ubergeek42/weechat-android
[3]: https://www.weechat.org/files/doc/stable/weechat_user.en.html#relay_commands
[4]: https://letsencrypt.org/
[5]: https://www.glowing-bear.org/

BIN
content/setup/2020-05-04-command-line-email-with-aerc/aerc-mail.jpg

Before

Width: 1231  |  Height: 646  |  Size: 332 KiB

195
content/setup/2020-05-04-command-line-email-with-aerc/index.md

@ -1,195 +0,0 @@
+++
title = "command line email with aerc"
date = 2020-05-04
insert_anchor_links = "left"
[taxonomies]
tags = ["setup", "email", "protonmail"]
[extra]
include_posts = true
toc = true
+++
I just set up command line email with my new work account and my [ProtonMail][1] account today! This article will be covering the full setup.
![aerc screenshot](aerc-mail.jpg)
## setting up proton-bridge
ProtonMail is an email service that end-to-end encrypts its users' emails with PGP. As a result, it doesn't speak SMTP or IMAP directly, since email clients wouldn't know how to undo the encryption anyway. That's why they've provided [proton-bridge][2], a ([now][3]) open-source SMTP/IMAP server that translates their own API calls into SMTP/IMAP for your email clients. For security, it's best to run this locally, so that emails aren't exposed over the network after they're decrypted.
For my setup, I have proton-bridge running as a systemd service. That means we have to deal with any interactive parts of the service so they're not popping up anymore.
Firstly, we want to build the bridge without support for the GUI. We won't be using it anyway, so this eliminates the Qt dependency.
Secondly, proton-bridge stores keys in an encrypted keyring, like [password-store][5]. My regular password-store is encrypted with my passphrase-protected GPG key, so I didn't want to use it since it'll be asking me for the passphrase again every time the timeout expires. We're going to make a separate GPG and password-store setup that will only be used for proton-bridge. Since it's all running locally anyway, we're _not_ to use a passphrase on this GPG key.
Authenticating only happens once, and the local SMTP/IMAP password doesn't change very often, so we won't really care about that. We'll bundle this up into a couple of nice scripts and then have it configured to start on startup!
### building proton-bridge
To build proton-bridge without the GUI, we'll need to grab a copy of the [source][2]. Clone the repo and then change directory to it:
```bash
git clone https://github.com/protonmail/proton-bridge
cd proton-bridge
```
As of now, there's no tagged releases, so let's just build straight from master. Peeking into the Makefile, there isn't a release build for the nogui option, so let's add it ourselves. Put this somewhere in the Makefile:
```make
build-nogui:
PROTONMAIL_ENV= go build ${BUILD_FLAGS_NOGUI} ./cmd/Desktop-Bridge
```
Then run `make build-nogui` and you should get a binary called `Desktop-Bridge`. Don't authenticate just quite yet; we want to set up the keychain first so it stores it in the right place.
### isolating the keychain
proton-bridge needs a keychain to store the keys that it gets from authenticating. The bridge supports [password-store][5] and GNOME keyring, but I'll be setting up password-store here. The goal now is to create a password-store instance that's isolated from the default installation so it doesn't require any interactive prompts.
For this part, I created two directories: the directory for the new GPG homedir, and the directory for the new password-store. If you're copy-pasting commands out of this post, I'd recommend you add these variables right now:
```bash
export PASSWORD_STORE_DIR=/path/to/password/store
export GNUPGHOME=/path/to/gpg/home
```
...obviously replacing the paths with paths that you choose. These variables are used by `pass` and `gpg` to overwrite the default directories they use, so it's important to set them up. The next step is to make sure they both exist:
```bash
mkdir -p $PASSWORD_STORE_DIR $GNUPGHOME
```
Now initialize the GPG key first. Run:
```bash
gpg --full-generate-key
```
There should be an interactive prompt. Go through and answer the questions however you see fit. I'd recommend you make the key 4096 bits, never expire, and have no passphrase. This key should never leave your local machine, and you will almost never use it directly, so there should be no problem.
Then, set up password-store. Run:
```bash
pass init [gpg-id]
```
where `[gpg-id]` is some identifier for the key you just created (name or email works).
At this point, you should test your configuration by running the `Desktop-Bridge` program with the `-c` option to open the prompt. Make sure you are able to log in and that rerunning the program should automatically run the bridge using the authenticated user without any interactive prompts.
```
Welcome to ProtonMail Bridge interactive shell
___....___
^^ __..-:'':__:..:__:'':-..__
_.-:__:.-:'': : : :'':-.:__:-._
.':.-: : : : : : : : : :._:'.
_ :.': : : : : : : : : : : :'.: _
[ ]: : : : : : : : : : : : : :[ ]
[ ]: : : : : : : : : : : : : :[ ]
:::::::::[ ]:__:__:__:__:__:__:__:__:__:__:__:__:__:[ ]:::::::::::
!!!!!!!!![ ]!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!![ ]!!!!!!!!!!!
^^^^^^^^^[ ]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[ ]^^^^^^^^^^^
[ ] [ ]
[ ] [ ]
jgs [ ] [ ]
~~^_~^~/ \~^-~^~ _~^-~_^~-^~_^~~-^~_~^~-~_~-^~_^/ \~^ ~~_ ^
>>> login
Username: iptq
Password:
Authenticating ...
Adding account ...
```
### automating the whole process
Now that the bridge is able to run independently, let's make a tiny wrapper script called `run-proton-bridge.sh` that calls it using the appropriate environment variables:
```bash
#!/bin/bash
# same as before
export PASSWORD_STORE_DIR=/path/to/password/store
export GNUPGHOME=/path/to/gpg/home
exec /path/to/Desktop-Bridge $@
```
Save this somewhere, give it executable permissions.
Finally, we can add a systemd service that runs this whole business whenever the network is available. I'd recommend adding this as a user service rather than a system service. You can put this somewhere like `$HOME/.config/systemd/user/proton-bridge.service`:
```systemd
[Unit]
After=network.target
[Service]
Restart=always
ExecStart=/path/to/run-proton-bridge.sh
[Install]
WantedBy=default.target
```
Run this service:
```
systemctl --user start proton-bridge
```
Enable it to have it auto-start:
```
systemctl --user enable proton-bridge
```
Bridge configuration should be complete at this point, so let's move on to configuring our mail client to work with it.
## setting up aerc
[aerc][4] is a new (**work-in-progress**) command-line mail client by Drew Devault. It features a familiar set of default keybinds, calls out to your favorite editor for composition, and DWIM for many other things. I just started using it today and had no problem getting used to the interface.
The setup for basic SMTP/IMAP email accounts is actually pretty trivial. When you run aerc for the first time, or whenever you run the command `:new-account`, an interactive screen is brought up prompting you for the details about your mailbox. If your mail provider doesn't work immediately, jump into `#aerc` on freenode; the folks there are super helpful with different mail providers.
### setting up aerc.. _with_ protonmail
If you've been keeping up, then all the pieces should be in place for aerc to work with ProtonMail. There's just a tiny bit of glue we have to add to put it all together.
**aerc does not allow untrusted certificates**. Since proton-bridge generates a self-signed cert, we'll need to trust this cert before we can do anything. There's not really an easy way to pull the certificate out, so I'd recommend just firing up the bridge and then connecting to it using the openssl client and then copy-pasting the certificate part:
```
$ openssl s_client -starttls imap -connect 127.0.0.1:1143 -showcerts
CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 C = CH, O = Proton Technologies AG, OU = ProtonMail, CN = 127.0.0.1
verify return:1
---
Certificate chain
0 s:C = CH, O = Proton Technologies AG, OU = ProtonMail, CN = 127.0.0.1
i:C = CH, O = Proton Technologies AG, OU = ProtonMail, CN = 127.0.0.1
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
---
Server certificate
subject=C = CH, O = Proton Technologies AG, OU = ProtonMail, CN = 127.0.0.1
issuer=C = CH, O = Proton Technologies AG, OU = ProtonMail, CN = 127.0.0.1
...
```
Take that huge chunk starting with the line `BEGIN CERTIFICATE` and ending with the line `END CERTIFICATE` and stick it into some file (ex. `protonmail.crt`). This is the self-signed cert we need to trust.
Now we'll need to tell our system to allow this cert. This varies from system to system; I'm on an Arch machine, so I ran:
```
sudo trust anchor --store protonmail.crt
```
After this, fire up `aerc` again, and your emails should start showing up.
[1]: https://protonmail.com/
[2]: https://github.com/ProtonMail/proton-bridge
[3]: https://protonmail.com/blog/bridge-open-source/
[4]: https://aerc-mail.org/

11
content/setup/_index.md

@ -1,11 +0,0 @@
+++
template = "blog.html"
insert_anchor_links = "left"
[extra]
include_posts = true
+++
# setup
These posts are tutorial-style articles for setting things up.

43
layouts/_default/baseof.html

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{ $style := resources.Get "sass/main.scss" | resources.ToCSS }}
<link rel="stylesheet" href="{{ $style.Permalink }}" />
</head>
<body>
<header>
<div id="header" class="container">
<a href="/" id="title" class="title nocolorlink">{{ .Site.Title }}</a>
<div id="nav">
{{- range $index, $page := .Site.Home.Pages -}}
{{- if ne $index 0 -}}
&nbsp;&middot;&nbsp;
{{- end -}}
<a href="{{ .RelPermalink }}" class="link">{{ $page.Title }}</a>
{{- end -}}
</div>
</div>
</header>
<div class="container" style="padding: 5px 40px;">
{{ block "content" . }}{{ end }}
</div>
<div style="text-align: center; font-weight: 200; margin-bottom: 40px;">
<p style="margin: 0;">
<a href="/tags" class="colorlink">tags</a> &middot;
<a href="/pages" class="colorlink">all pages</a>
</p>
<p style="margin: 0;">
written by michael zhang.
<a href="https://git.iptq.io/michael/blog" class="colorlink" target="_blank">source</a>
</p>
</div>
</body>
</html>

5
layouts/generic/list.html

@ -0,0 +1,5 @@
{{- define "content" -}}
{{ .Content }}
{{- end -}}

7
layouts/home.html

@ -0,0 +1,7 @@
{{- define "content" -}}
{{ $posts := .GetPage "/posts" }}
{{ partial "post-list" $posts }}
{{- end -}}

19
layouts/partials/post-list.html

@ -0,0 +1,19 @@
<table style="width: 100%;">
{{- range .Pages -}}
<tr class="postlisting-row">
<td>
<span style="font-size: 1.2em;">
<a href="{{ .Permalink }}" class="brand-colorlink">{{ .Title }}</a>
</span>
<br />
<small>
{{ .ReadingTime }} min read -
Posted
on {{ .Date.Format "Mon Jan 2 2006" }}
</small>
</td>
</tr>
{{- end -}}
</table>

7
layouts/posts/list.html

@ -0,0 +1,7 @@
{{- define "content" -}}
{{ .Content }}
{{ partial "post-list" . }}
{{- end -}}

20
layouts/posts/single.html

@ -0,0 +1,20 @@
{{- define "content" -}}
<a href="../">&laquo; back</a>
<h1 style="margin: 0;">{{ .Title }}</h1>
<small style="display: block; margin-bottom: 20px;">
Posted
on {{ .Date }}
- {{ .ReadingTime }} min read
</small>
<div id="content">{{ .Content }}</div>
<hr />
<small>
End.
</small>
{{- end -}}

22
sass/_graph.scss

@ -1,22 +0,0 @@
.graphviz {
margin: 24px auto;
svg {
max-width: 100%;
width: 100%;
.graph.default-coloring {
ellipse, path, polygon {
stroke: $text-color;
}
text {
fill: $text-color;
}
* {
font-family: $sansfont;
}
}
}
}

56
static/.well-known/keybase.txt

@ -1,56 +0,0 @@
==================================================================
https://keybase.io/michaelz
--------------------------------------------------------------------
I hereby claim:
* I am an admin of https://mzhang.me
* I am michaelz (https://keybase.io/michaelz) on keybase.
* I have a public key ASCEhMaJMl3DkJKGSXc8q1Nr5FIMVapUMwtEbQkCH7vIQAo
To do so, I am signing this object:
{
"body": {
"key": {
"eldest_kid": "01208484c689325dc390928649773cab536be4520c55aa54330b446d09021fbbc8400a",
"host": "keybase.io",
"kid": "01208484c689325dc390928649773cab536be4520c55aa54330b446d09021fbbc8400a",
"uid": "601f28af1374f456f494088799b48f19",
"username": "michaelz"
},
"merkle_root": {
"ctime": 1534063641,
"hash": "3d90ca7aa9d1224c4001d3d74c6eece550b1d573997ad1195747524aed8ca8c78e906659279835411ff6318f32e5cc3473bcb17d59e805bc4c09c894a85b2133",
"hash_meta": "4b048995ed39d76081818e6a3e1e5ecbf2a0f162dd919ce85c9fbbd8950f623c",
"seqno": 3444517
},
"service": {
"entropy": "BOxM8Iu2jNXojZrry8ZRRLQD",
"hostname": "mzhang.me",
"protocol": "https:"
},
"type": "web_service_binding",
"version": 2
},
"client": {
"name": "keybase.io go client",
"version": "2.6.0"
},
"ctime": 1534063667,
"expire_in": 504576000,
"prev": "df6a6156d48e0f9046ef4affcc78b05f4c82da914d659e02341b8e8d3c217989",
"seqno": 7,
"tag": "signature"
}