Recent changes to this wiki:

diff --git a/doc/bugs/master_fails_to_build.mdwn b/doc/bugs/master_fails_to_build.mdwn
new file mode 100644
index 0000000..97675d5
--- /dev/null
+++ b/doc/bugs/master_fails_to_build.mdwn
@@ -0,0 +1,30 @@
+```
+Downloading lts-9.0 build plan ...
+Downloaded lts-9.0 build plan.
+Updating package index Hackage (mirrored at https://s3.amazonaws.com/hackage.fpcomplete.com/) ...
+Selected mirror https://s3.amazonaws.com/hackage.fpcomplete.com/
+Downloading root
+Selected mirror https://s3.amazonaws.com/hackage.fpcomplete.com/
+Downloading timestamp
+Downloading snapshot
+Downloading mirrors
+Cannot update index (no local copy)
+Downloading index
+Updated package list downloaded
+Populating index cache ...
+Populated index cache.
+
+Error: While constructing the build plan, the following exceptions were encountered:
+
+In the dependencies for debug-me-1.20170810:
+    posix-pty must match (>=0.2.1), but the stack configuration has no specified version (latest applicable is 0.2.1.1)
+needed since debug-me-1.20170810 is a build target.
+
+Recommended action: try adding the following to your extra-deps in /builddir/debug-me-1.20170810/stack.yaml:
+- posix-pty-0.2.1.1
+
+You may also want to try the 'stack solver' command
+Plan construction failed.
+```
+
+I couldn't see posix-pty in the lts package list? Was it erroneously removed from stack.yaml?

add news item for debug-me 1.20170810
diff --git a/doc/news/version_1.20170810.mdwn b/doc/news/version_1.20170810.mdwn
new file mode 100644
index 0000000..3597a23
--- /dev/null
+++ b/doc/news/version_1.20170810.mdwn
@@ -0,0 +1,4 @@
+debug-me 1.20170810 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+   * Fix build with websockets-0.10.0.0 which did not support compression.
+   * Update to lts-9.0."""]]
\ No newline at end of file

report bug
diff --git a/doc/bugs/fails_to_build_against_current_LTS_Haskell.mdwn b/doc/bugs/fails_to_build_against_current_LTS_Haskell.mdwn
new file mode 100644
index 0000000..52b9faf
--- /dev/null
+++ b/doc/bugs/fails_to_build_against_current_LTS_Haskell.mdwn
@@ -0,0 +1,15 @@
+debug-me fails to build against LTS 8.23:
+
+    ...
+    [20 of 32] Compiling WebSockets       ( WebSockets.hs,
+    dist/build/debug-me/debug-me-tmp/WebSockets.o )
+    
+    WebSockets.hs:96:9: error:
+        `fromDataMessage' is not a (visible) method of class `WebSocketsData'
+    
+    WebSockets.hs:123:11: error:
+        Not in scope: `connectionCompressionOptions'
+    Makefile:29: recipe for target 'debug-me' failed
+    make[2]: *** [debug-me] Error 1
+
+--spwhitton

add
diff --git a/doc/comments.mdwn b/doc/comments.mdwn
new file mode 100644
index 0000000..e19962b
--- /dev/null
+++ b/doc/comments.mdwn
@@ -0,0 +1,9 @@
+[[!sidebar content="""
+[[!inline pages="comment_pending(*)" feedfile=pendingmoderation
+description="comments pending moderation" show=-1]]
+Comments in the [[!commentmoderation desc="moderation queue"]]:
+[[!pagecount pages="comment_pending(*)"]]
+"""]]
+
+Recent comments posted to this site:
+[[!inline pages="comment(*)" template="comment"]]

add
diff --git a/doc/faq.mdwn b/doc/faq.mdwn
index 6884ec0..10d6422 100644
--- a/doc/faq.mdwn
+++ b/doc/faq.mdwn
@@ -62,6 +62,12 @@ For this to work, you have to have a copy of the session log file. This is
 why the debug-me server will email it to you at the end of the session, to
 make sure you get a copy.
 
+To verify a session log file, run: `debug-me --verify debug-me.log`
+That will display the gpg key of the developer who was active in
+that session, and verify the integrety of the log file.
+
+A debug-me log file can be replayed by running: `debug-me --reply debug-me.log`
+
 ### For Developers
 
 #### What do I need to do to start using debug-me?

add news item for debug-me 1.20170520
diff --git a/doc/news/version_1.20170520.mdwn b/doc/news/version_1.20170520.mdwn
new file mode 100644
index 0000000..094f328
--- /dev/null
+++ b/doc/news/version_1.20170520.mdwn
@@ -0,0 +1,13 @@
+debug-me 1.20170520 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+   * debug-me is available in Debian unstable.
+   * gpg keyrings in /usr/share/debug-me/ will be checked
+     to see if a connecting person is a known developer of software
+     installed on the system, and so implicitly trusted already.
+     Software packages/projects can install keyrings to that location.
+     (Thanks to Sean Whitton for the idea.)
+   * make install installs /usr/share/debug-me/a\_debug-me\_developer.gpg,
+     which contains the key of Joey Hess. (stack and cabal installs don't
+     include this file because they typically don't install system-wide)
+   * debug-me.cabal: Added dependency on time.
+   * stack.yaml: Update to new posix-pty version."""]]
\ No newline at end of file

developer keyring verification
* gpg keyrings in /usr/share/debug-me/ will be checked
to see if a connecting person is a known developer of software
installed on the system, and so implicitly trusted already.
Software packages/projects can install keyrings to that location.
(Thanks to Sean Whitton for the idea.)
* make install will install /usr/share/debug-me/debug-me_developer.gpg,
which contains the key of Joey Hess. (stack and cabal installs don't
include this file because they typically don't install system-wide)
* debug-me.cabal: Added dependency on time.
This commit was sponsored by Francois Marier on Patreon.
diff --git a/CHANGELOG b/CHANGELOG
index bafd9e9..e8ea5c1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,15 @@
 debug-me (1.20170510) UNRELEASED; urgency=medium
 
   * debug-me is available in Debian unstable.
+  * gpg keyrings in /usr/share/debug-me/ will be checked
+    to see if a connecting person is a known developer of software
+    installed on the system, and so implicitly trusted already.
+    Software packages/projects can install keyrings to that location.
+    (Thanks to Sean Whitton for the idea.)
+  * make install installs /usr/share/debug-me/a_debug-me_developer.gpg,
+    which contains the key of Joey Hess. (stack and cabal installs don't
+    include this file because they typically don't install system-wide)
+  * debug-me.cabal: Added dependency on time.
   * stack.yaml: Update to new posix-pty version.
 
  -- Joey Hess <id@joeyh.name>  Sat, 20 May 2017 13:47:27 -0400
diff --git a/ControlWindow.hs b/ControlWindow.hs
index c5a6be9..bd79d0f 100644
--- a/ControlWindow.hs
+++ b/ControlWindow.hs
@@ -15,6 +15,7 @@ import ControlSocket
 import VirtualTerminal
 import Gpg
 import Gpg.Wot
+import Gpg.Keyring
 import Output
 
 import System.IO
@@ -163,6 +164,8 @@ askToAllow ochan promptchan responsechan k@(GpgSigned pk _ _) = do
 			ws <- downloadWotStats gpgkeyid
 			putStrLn $ unlines $ map sanitizeForDisplay $
 				describeWot ws ss
+			mapM_ (putStrLn . keyringToDeveloperDesc ws)
+				=<< findKeyringsContaining gpgkeyid
 			promptconnect
   where
 	promptconnect :: IO ()
diff --git a/Gpg/Keyring.hs b/Gpg/Keyring.hs
new file mode 100644
index 0000000..a0fa242
--- /dev/null
+++ b/Gpg/Keyring.hs
@@ -0,0 +1,73 @@
+{- Copyright 2017 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU AGPL version 3 or higher.
+ -}
+
+-- | Gpg keyrings for debug-me
+
+module Gpg.Keyring where
+
+import Gpg
+import qualified Gpg.Wot
+
+import System.FilePath
+import Data.Char
+import System.Directory
+import Data.Time.Clock
+import Data.Time.Format
+import System.Process
+import System.Exit
+
+keyringDir :: FilePath
+keyringDir = "/usr/share/debug-me/keyring"
+
+data Keyring = Keyring FilePath UTCTime
+
+keyringToDeveloperDesc :: Maybe (Gpg.Wot.WotStats) -> Keyring -> String
+keyringToDeveloperDesc mws (Keyring f mtime) =
+	name ++ " is " ++ desc ++ " \t(as of " ++ showtime mtime ++ ")"
+  where
+	name = maybe "This person" Gpg.Wot.wotStatName mws
+	desc = map sanitize $ dropExtension $ takeFileName f
+	sanitize '_' = ' '
+	sanitize c
+		| isAlphaNum c || c `elem` "-+" = c
+		| otherwise = '?'
+	showtime = formatTime defaultTimeLocale "%c"
+
+findKeyringsContaining :: GpgKeyId -> IO [Keyring]
+findKeyringsContaining k = 
+	go [] . map (keyringDir </>) =<< getDirectoryContents keyringDir
+  where
+	go c [] = return c
+	go c (f:fs) = do
+		isfile <- doesFileExist f
+		if isfile && takeExtension f == ".gpg"
+			then do
+				inkeyring <- isInKeyring k f
+				if inkeyring
+					then do
+						mtime <- getModificationTime f
+						let keyring = Keyring f mtime
+						go (keyring : c) fs
+					else go c fs
+			else go c fs
+
+-- | Check if the gpg key is included in the keyring file.
+--
+-- Similar to gpgv, this does not check if the key is revoked or expired,
+-- only if it's included in the keyring.
+isInKeyring :: GpgKeyId -> FilePath -> IO Bool
+isInKeyring (GpgKeyId k) f = do
+	-- gpg assumes non-absolute keyring files are relative to ~/.gnupg/
+	absf <- makeAbsolute f
+	let p = proc "gpg"
+		-- Avoid reading any keyrings except the specified one.
+		[ "--no-options"
+		, "--no-default-keyring"
+		, "--no-auto-check-trustdb"
+		, "--keyring", absf
+		, "--list-key", k
+		]
+	(exitcode, _, _) <- readCreateProcessWithExitCode p ""
+	return (exitcode == ExitSuccess)
diff --git a/Gpg/Wot.hs b/Gpg/Wot.hs
index b29ccc7..2a6d541 100644
--- a/Gpg/Wot.hs
+++ b/Gpg/Wot.hs
@@ -107,7 +107,7 @@ describeWot (Just ws) (StrongSetAnalysis ss)
 		, theirname ++ " is probably a real person."
 		]
   where
-	theirname = stripEmail (uid (key ws))
+	theirname = wotStatName ws
 	sigs = cross_sigs ws ++ other_sigs ws
 	bestconnectedsigs = sortOn rank sigs
 describeWot Nothing _ =
@@ -115,5 +115,8 @@ describeWot Nothing _ =
 	, "Their identity cannot be verified!"
 	]
 
+wotStatName :: WotStats -> String
+wotStatName ws = stripEmail (uid (key ws))
+
 stripEmail :: String -> String
 stripEmail = unwords . takeWhile (not . ("<" `isPrefixOf`)) . words
diff --git a/Makefile b/Makefile
index 3244942..01eaad3 100644
--- a/Makefile
+++ b/Makefile
@@ -61,6 +61,9 @@ install-files: debug-me install-mans
 	install -m 0755 debug-me.init $(DESTDIR)$(PREFIX)/etc/init.d/debug-me
 	install -d $(DESTDIR)$(PREFIX)/etc/default
 	install -m 0644 debug-me.default $(DESTDIR)$(PREFIX)/etc/default/debug-me
+	install -d $(DESTDIR)$(PREFIX)/usr/share/debug-me/keyring
+	install -m 0655 developer-keyring.gpg \
+		$(DESTDIR)$(PREFIX)/usr/share/debug-me/keyring/a_debug-me_developer.gpg
 
 install-mans:
 	install -d $(DESTDIR)$(PREFIX)/usr/share/man/man1
diff --git a/debug-me.1 b/debug-me.1
index a0e108a..251e636 100644
--- a/debug-me.1
+++ b/debug-me.1
@@ -14,13 +14,16 @@ problem. Making your problem their problem gets it fixed fast.
 A debug-me session is logged and signed with the developer's GnuPG 
 key, producing a chain of evidence of what they saw and what they did. 
 So the developer's good reputation is leveraged to make debug-me secure.
+If you trust a developer to ship software to your computer,
+you can trust them to debug-me.
 .PP
 When you start debug-me without any options, it will connect to a debug-me
 server, and print out an url that you can give to the developer to get
 them connected to you. Then debug-me will show you their GnuPG key and who
-has signed it. If the developer has a good reputation, you can proceed
-to let them type into your console in a debug-me session. Once the
-session is done, the debug-me server will email you the signed
+has signed it, and will let you know if they are a known developer
+of software on your computer. If the developer has a good reputation, you
+can proceed to let them type into your console in a debug-me session. Once
+the session is done, the debug-me server will email you the signed
 evidence of what the developer did in the session.
 .PP
 It's a good idea to watch the debug-me session. The developer should be
@@ -101,6 +104,10 @@ exits.
 .IP "~/.debug-me/log/remote/"
 When using debug-me to connect to a remote session, the session will be
 logged to here.
+.UP "/usr/share/debug-me/keyring/*.gpg"
+When verifying a developer's gpg key, debug-me checks if it's listed in 
+the keyrings in this directory, which can be provided by software installed
+on the computer.
 .SH SEE ALSO
 <https://debug-me.branchable.com/>
 .PP
diff --git a/debug-me.cabal b/debug-me.cabal
index 10b184e..3750f00 100644
--- a/debug-me.cabal
+++ b/debug-me.cabal
@@ -20,13 +20,16 @@ Description:
  A debug-me session is logged and signed with the developer's GnuPG
  key, producing a chain of evidence of what they saw and what they did.
  So the developer's good reputation is leveraged to make debug-me secure.
+ If you trust a developer to ship software to your computer,

(Diff truncated)
move unsafe hashing out of instance to avoid misuse
Avoids breaking backwards compat and should avoid future foot-shooting.
diff --git a/Crypto.hs b/Crypto.hs
index efc754f..2fe27e0 100644
--- a/Crypto.hs
+++ b/Crypto.hs
@@ -31,7 +31,7 @@ class Signed t where
 instance Hashable a => Signed (Activity a) where
 	getSignature = activitySignature
 	hashExceptSignature (Activity a mpa mpe mt _s) = hash $
-		Tagged "Activity" [hash a, hash mpa, hash mpe, hash mt]
+		Tagged "Activity" [hash a, hashOfMaybeUnsafe mpa, hashOfMaybeUnsafe mpe, hash mt]
 
 instance Signed Control where
 	getSignature = controlSignature
diff --git a/Hash.hs b/Hash.hs
index 89b0384..cb90c85 100644
--- a/Hash.hs
+++ b/Hash.hs
@@ -41,7 +41,7 @@ instance Hashable a => Hashable (Tagged a) where
 
 instance Hashable a => Hashable (Activity a) where
 	hash (Activity a mps mpe mt s) = hash $ Tagged "Activity"
-		[hash a, hash mps, hash mpe, hash mt, hash s]
+		[hash a, hashOfMaybeUnsafe mps, hashOfMaybeUnsafe mpe, hash mt, hash s]
 
 instance Hashable Entered where
 	hash v = hash $ Tagged "Entered"
@@ -52,7 +52,7 @@ instance Hashable Seen where
 
 instance Hashable ControlAction where
 	hash (EnteredRejected h1 h2) = hash $ Tagged "EnteredRejected"
-		[hash h1, hash h2]
+		[hash h1, hashOfMaybeUnsafe h2]
 	hash (SessionKey pk v) = hash $ Tagged "SessionKey" [hash pk, hash v]
 	hash (SessionKeyAccepted pk) = hash $ Tagged "SessionKeyAccepted" pk
 	hash (SessionKeyRejected pk) = hash $ Tagged "SessionKeyRejected" pk
@@ -83,7 +83,21 @@ instance Hashable ElapsedTime where
 instance Hashable [Hash] where
 	hash = hash . B.concat . map (val . hashValue)
 
--- | Hash empty string for Nothing
+-- | Hash a Maybe Hash, such that 
+--   hash Nothing /= hash (Just (hash (mempty :: B.ByteString)))
 instance Hashable (Maybe Hash) where
+	hash (Just v) = hash (val (hashValue v))
 	hash Nothing = hash (mempty :: B.ByteString)
-	hash (Just v) = hash v
+
+-- | Hash a Maybe Hash using the Hash value as-is, or the hash of the empty
+-- string for Nothing.
+--
+-- Note that this is only safe to use when the input value can't possibly
+-- itself be the hash of an empty string. For example, the hash of an
+-- Activity is safe, because it's the hash of a non-empty string.
+--
+-- This is only used to avoid breaking backwards compatability; the
+-- above instance for Maybe Hash should be used for anything new.
+hashOfMaybeUnsafe :: Maybe Hash -> Hash
+hashOfMaybeUnsafe (Just v) = hash v
+hashOfMaybeUnsafe Nothing = hash (mempty :: B.ByteString)
diff --git a/doc/protocol/comment_4_6c6cd957b3e4db5b77f87b13c4e35e6b._comment b/doc/protocol/comment_4_6c6cd957b3e4db5b77f87b13c4e35e6b._comment
new file mode 100644
index 0000000..ed1bb32
--- /dev/null
+++ b/doc/protocol/comment_4_6c6cd957b3e4db5b77f87b13c4e35e6b._comment
@@ -0,0 +1,35 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 4"""
+ date="2017-05-20T17:53:29Z"
+ content="""
+So the problem comes from the hash
+"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
+-- if that's intended to be a `Maybe Hash` that's the hash of a `ByteString`,
+we can't tell if it was produced by hashing `Nothing`, or hashing 
+`Just (mempty :: ByteString)`
+
+Double hashing would avoid this ambiguity, but it does also break backwards
+compatability of the debug-me protocol and logs. It's still early enough to
+perhaps do that without a great deal of bother, but it's not desirable.
+
+debug-me does not appear to be actually affected by this currently. The only
+`Maybe Hash` in debug-me is used for a hash of values of type `Activity`
+and `Entered`, not the hash of a `ByteString`. So, as far as the debug-me
+protocol goes, the above hash value is unambiguously the hash of `Nothing`;
+there's no `Activity` or `Entered` that hashes to that value. 
+(Barring of course, a cryptographic hash collision which would need SHA2
+to be broken to be exploited.)
+
+So, I'd like to clean this up, to avoid any problems creeping in if
+a `Maybe Hash` got used for the hash of a `ByteString`. But, I don't feel
+it's worth breaking backwards compatibility for.
+
+(I tried adding a phantom type to Hash, so the instance could be only
+for `Maybe (Hash Activity)`, but quickly ran into several complications.)
+
+What I've done is fixed the instance to work like you suggested,
+but kept the old function as `hashOfMaybeUnsafe` and used it where
+necessary. This way, anything new will use the fixed instance and we don't
+break back-compat.
+"""]]

stack.yaml: Update to new posix-pty version.
diff --git a/CHANGELOG b/CHANGELOG
index 65f54c2..2aecd3d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+debug-me (1.20170510) UNRELEASED; urgency=medium
+
+  * stack.yaml: Update to new posix-pty version.
+
+ -- Joey Hess <id@joeyh.name>  Sat, 20 May 2017 13:47:27 -0400
+
 debug-me (1.20170509) unstable; urgency=medium
 
   * Server: Use "postmaster" as default --from-email address
diff --git a/doc/bugs/Update_to_posix-pty_0.2.1.1.mdwn b/doc/bugs/Update_to_posix-pty_0.2.1.1.mdwn
index c15535f..fcf38e3 100644
--- a/doc/bugs/Update_to_posix-pty_0.2.1.1.mdwn
+++ b/doc/bugs/Update_to_posix-pty_0.2.1.1.mdwn
@@ -1 +1,3 @@
 Latest version of posix-pty fixes support for musl. Would it be possible to bump the dependency version & cut a new release?
+
+> [[done]] --[[Joey]]
diff --git a/doc/bugs/Update_to_posix-pty_0.2.1.1/comment_1_fb0d1b1adfbe02e168d94bf80a254da8._comment b/doc/bugs/Update_to_posix-pty_0.2.1.1/comment_1_fb0d1b1adfbe02e168d94bf80a254da8._comment
new file mode 100644
index 0000000..4c0940d
--- /dev/null
+++ b/doc/bugs/Update_to_posix-pty_0.2.1.1/comment_1_fb0d1b1adfbe02e168d94bf80a254da8._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2017-05-20T17:46:23Z"
+ content="""
+You must mean in the stack.yaml because the cabal file has no upper bound.
+
+I've bumped the version in stack.yaml, will release maybe this weekend,
+but ping if it goes to long before a release.
+"""]]
diff --git a/stack.yaml b/stack.yaml
index 784d3fe..abbdc98 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -2,6 +2,6 @@ packages:
 - '.'
 resolver: lts-8.12
 extra-deps:
-- posix-pty-0.2.1
+- posix-pty-0.2.1.1
 - websockets-0.11.1.0
 explicit-setup-deps:

good idea!
diff --git a/doc/todo/use_distribution_keyrings/comment_1_e383699dbed1890a16e3dfa80bd60905._comment b/doc/todo/use_distribution_keyrings/comment_1_e383699dbed1890a16e3dfa80bd60905._comment
new file mode 100644
index 0000000..3270c33
--- /dev/null
+++ b/doc/todo/use_distribution_keyrings/comment_1_e383699dbed1890a16e3dfa80bd60905._comment
@@ -0,0 +1,28 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2017-05-20T17:33:53Z"
+ content="""
+Very good idea!
+
+I suppose all it needs is a list of keyrings to check, and if it finds a
+key there, it can say "John Doe is a Debian developer" rather than the current
+"John Doe is probably a real person".
+
+This could be extended beyond distributions; individual software programs
+could also ship keyrings with their developer(s).
+
+So, how about rather than a hardcoded distro-specific list of keyrings,
+make debug-me look in /usr/share/debug-me/keyring/$project.gpg
+There could be an accompnying file $project.desc that describes the
+relationship to the project that being in their keyring entails. Eg,
+"Relationship: Debian developer" in debian.desc.
+
+In the debian package of debug-me, you could then symlink
+/usr/share/keyrings/debian-keyring.gpg to the debug-me keyring directory.
+
+The only risk is that some shady software project ships a keyring with a
+.desc file that contains "Debian developer", so debug-me will claim a bogus
+key is the key of a debian developer. But if a debug-me user is using such
+shady software, it's probably rooted their computer already..
+"""]]

diff --git a/doc/bugs/Update_to_posix-pty_0.2.1.1.mdwn b/doc/bugs/Update_to_posix-pty_0.2.1.1.mdwn
new file mode 100644
index 0000000..c15535f
--- /dev/null
+++ b/doc/bugs/Update_to_posix-pty_0.2.1.1.mdwn
@@ -0,0 +1 @@
+Latest version of posix-pty fixes support for musl. Would it be possible to bump the dependency version & cut a new release?

apt-get instructions for debug-me-server
diff --git a/doc/servers.mdwn b/doc/servers.mdwn
index ed7176c..571eb36 100644
--- a/doc/servers.mdwn
+++ b/doc/servers.mdwn
@@ -10,9 +10,8 @@ Your server needs to have a working mail transport agent so it can email
 logs to debug-me users.
 
 The debug-me source package includes an init script and a systemd service
-file. Running "make install" as root will install everything. Distribution
-packages of debug-me might put the server stuff in a separate package than
-the main debug-me package.
+file. Running "make install" as root will install everything.  Or on Debian 10 or
+later or Ubuntu 17.10 or later, `apt-get install debug-me-server`.
 
 debug-me has a server list built into it of servers it uses. To get your
 server added to the list, file a [[todo]] item with the url for your server,

add apt-get installation instructions
diff --git a/doc/install.mdwn b/doc/install.mdwn
index 2f8d24f..f6b1dc7 100644
--- a/doc/install.mdwn
+++ b/doc/install.mdwn
@@ -7,6 +7,10 @@ To use:
 	tar xf debug-me-standalone-amd64.tar.gz
 	debug-me/debug-me
 
+## Distributions
+
+Debian 10 or later or Ubuntu 17.10 or later: `apt-get install debug-me`
+
 ## building from source
 
 Clone debug-me's git repository from <git://debug-me.branchable.com/>

add example output to feature suggestion
diff --git a/doc/todo/use_distribution_keyrings.mdwn b/doc/todo/use_distribution_keyrings.mdwn
index 0191b13..df21588 100644
--- a/doc/todo/use_distribution_keyrings.mdwn
+++ b/doc/todo/use_distribution_keyrings.mdwn
@@ -1,5 +1,7 @@
 In addition to the web-of-trust checking debug-me already does, it could also inform the user whether keys are present in distribution keyrings, such as `/usr/share/keyrings/debian-keyring.gpg`.  This would be especially relevant when it is distribution issues that are to be debugged with debug-me: the person connecting is also capable of pushing updates to the usre's machine.
 
+Example output: `Sean Whitton is an official Debian Developer (information accurate as of YYYY-MM-DD)` where the date comes from the version of the `debian-keyring` package.
+
 Distribution packagers of debug-me could add the keyrings to be checked in this way to a configuration file, or possibly just hardcode them somewhere in debug-me's source.
 
 --spwhitton

post feature suggestion
diff --git a/doc/todo/use_distribution_keyrings.mdwn b/doc/todo/use_distribution_keyrings.mdwn
new file mode 100644
index 0000000..0191b13
--- /dev/null
+++ b/doc/todo/use_distribution_keyrings.mdwn
@@ -0,0 +1,5 @@
+In addition to the web-of-trust checking debug-me already does, it could also inform the user whether keys are present in distribution keyrings, such as `/usr/share/keyrings/debian-keyring.gpg`.  This would be especially relevant when it is distribution issues that are to be debugged with debug-me: the person connecting is also capable of pushing updates to the usre's machine.
+
+Distribution packagers of debug-me could add the keyrings to be checked in this way to a configuration file, or possibly just hardcode them somewhere in debug-me's source.
+
+--spwhitton

add news item for debug-me 1.20170509
diff --git a/doc/news/version_1.20170509.mdwn b/doc/news/version_1.20170509.mdwn
new file mode 100644
index 0000000..7ec6d4b
--- /dev/null
+++ b/doc/news/version_1.20170509.mdwn
@@ -0,0 +1,11 @@
+debug-me 1.20170509 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+   * Server: Use "postmaster" as default --from-email address
+     rather than "unknown@server".
+   * Server: DEBUG\_ME\_FROM\_EMAIL can be used to specify the --from-email.
+     This is used in debug-me.default to encourage configuring it.
+     Thanks, Sean Whitton.
+   * Avoid crash when --use-server is given an url that does not
+     include a path.
+   * Fix bug that prevented creating ~/.debug-me/log/remote/
+     when ~/.debug-me/ didn't already exist."""]]
\ No newline at end of file

Added a comment
diff --git a/doc/protocol/comment_3_6338e14886f146eb5d2f9c9316e1f7de._comment b/doc/protocol/comment_3_6338e14886f146eb5d2f9c9316e1f7de._comment
new file mode 100644
index 0000000..657a20a
--- /dev/null
+++ b/doc/protocol/comment_3_6338e14886f146eb5d2f9c9316e1f7de._comment
@@ -0,0 +1,22 @@
+[[!comment format=mdwn
+ username="https://www.joachim-breitner.de/"
+ nickname="nomeata"
+ avatar="http://cdn.libravatar.org/avatar/a2112893817513537c6a2c228c04c138a2f68bba57121ab7f267de58fc5171d7"
+ subject="comment 3"
+ date="2017-05-07T03:20:38Z"
+ content="""
+Still not good, I think, as the instance `Hashable Hash` has `hash = id`, so
+
+       hash Nothing
+    = hash ()
+    = id (hash ())
+    = hash (Just (hash ())
+
+ and we have a collision at type `Maybe Hash`.
+
+What would work is to do the same that `Hashable []` does, i.e. has the hash again:
+
+    hash (Just v) = hash (val (hashValue v))
+    hash Nothing = hash (mempty :: B.ByteString)
+
+"""]]

removed
diff --git a/doc/protocol/comment_3_e2d91431651d3ffe9072bd110b2fdc1d._comment b/doc/protocol/comment_3_e2d91431651d3ffe9072bd110b2fdc1d._comment
deleted file mode 100644
index 1318d16..0000000
--- a/doc/protocol/comment_3_e2d91431651d3ffe9072bd110b2fdc1d._comment
+++ /dev/null
@@ -1,19 +0,0 @@
-[[!comment format=mdwn
- username="https://www.joachim-breitner.de/"
- nickname="nomeata"
- avatar="http://cdn.libravatar.org/avatar/a2112893817513537c6a2c228c04c138a2f68bba57121ab7f267de58fc5171d7"
- subject="comment 3"
- date="2017-05-07T03:19:14Z"
- content="""
-Still not good, I think, as the instance `Hashable Hash` has `hash = id`, so
-```
-hash Nothing = hash () = id (hash ()) = hash (Just (hash ())
-```
-and we have a collision at type `Maybe Hash`.
-
-What would work is to do the same that `Hashable []` does, i.e. has the hash again:
-```
-    hash (Just v) = hash (val (hashValue v))
-    hash Nothing = hash (mempty :: B.ByteString)
-```
-"""]]

Added a comment
diff --git a/doc/protocol/comment_3_e2d91431651d3ffe9072bd110b2fdc1d._comment b/doc/protocol/comment_3_e2d91431651d3ffe9072bd110b2fdc1d._comment
new file mode 100644
index 0000000..1318d16
--- /dev/null
+++ b/doc/protocol/comment_3_e2d91431651d3ffe9072bd110b2fdc1d._comment
@@ -0,0 +1,19 @@
+[[!comment format=mdwn
+ username="https://www.joachim-breitner.de/"
+ nickname="nomeata"
+ avatar="http://cdn.libravatar.org/avatar/a2112893817513537c6a2c228c04c138a2f68bba57121ab7f267de58fc5171d7"
+ subject="comment 3"
+ date="2017-05-07T03:19:14Z"
+ content="""
+Still not good, I think, as the instance `Hashable Hash` has `hash = id`, so
+```
+hash Nothing = hash () = id (hash ()) = hash (Just (hash ())
+```
+and we have a collision at type `Maybe Hash`.
+
+What would work is to do the same that `Hashable []` does, i.e. has the hash again:
+```
+    hash (Just v) = hash (val (hashValue v))
+    hash Nothing = hash (mempty :: B.ByteString)
+```
+"""]]

idea
diff --git a/doc/todo/javascript_client.mdwn b/doc/todo/javascript_client.mdwn
new file mode 100644
index 0000000..72bf4da
--- /dev/null
+++ b/doc/todo/javascript_client.mdwn
@@ -0,0 +1,3 @@
+Since debug-me runs over websockets, it should be possible to compile it to
+javascript and produce a web page that can display and even interact with a
+debug-me session. Just an idea. --[[Joey]]

fix from nomeata's review
He pointed out that Just () and Nothing would hash the same.
Luckily Maybe Hash is the only Maybe type that needs to be hashed,
so specialize the instance.
diff --git a/Hash.hs b/Hash.hs
index 8a33803..a76e0b4 100644
--- a/Hash.hs
+++ b/Hash.hs
@@ -84,7 +84,7 @@ instance Hashable [Hash] where
 	hash = hash . B.concat . map (val . hashValue)
 
 -- | Hash empty string for Nothing
-instance Hashable v => Hashable (Maybe v) where
+instance Hashable (Maybe Hash) where
 	hash Nothing = hash ()
 	hash (Just v) = hash v
 
diff --git a/doc/protocol/comment_2_4a25b8ee6e438a031e875078ffb1d125._comment b/doc/protocol/comment_2_4a25b8ee6e438a031e875078ffb1d125._comment
new file mode 100644
index 0000000..935570d
--- /dev/null
+++ b/doc/protocol/comment_2_4a25b8ee6e438a031e875078ffb1d125._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 2"""
+ date="2017-05-06T01:34:39Z"
+ content="""
+Thanks for that review. That would indeed be bad. To avoid that
+potential problem, I've specialized the instance to 
+`Hashable (Maybe Hash)`, which is the only Maybe value that currently
+needs to be hashed.
+"""]]

Added a comment: Code smell in hashing
diff --git a/doc/protocol/comment_1_44d3ac18bf10c1644a73855c01868ab3._comment b/doc/protocol/comment_1_44d3ac18bf10c1644a73855c01868ab3._comment
new file mode 100644
index 0000000..64eed07
--- /dev/null
+++ b/doc/protocol/comment_1_44d3ac18bf10c1644a73855c01868ab3._comment
@@ -0,0 +1,19 @@
+[[!comment format=mdwn
+ username="https://www.joachim-breitner.de/"
+ nickname="nomeata"
+ avatar="http://cdn.libravatar.org/avatar/a2112893817513537c6a2c228c04c138a2f68bba57121ab7f267de58fc5171d7"
+ subject="Code smell in hashing"
+ date="2017-05-05T23:51:14Z"
+ content="""
+Hi Joey,
+
+I looked through http://source.debug-me.branchable.com/?p=source.git;a=blob;f=Hash.hs;hb=HEAD and since this probably scurity-relevant, allow me to be nitpicky:
+
+```
+instance Hashable v => Hashable (Maybe v) where
+  hash Nothing = hash ()
+  hash (Just v) = hash v
+```
+
+will hash the distinct values `Just ()` and `Nothing` identically. Maybe you don't have any `Maybe ()` type around, but in that case you should maybe document that requirement.
+"""]]

wording
diff --git a/debug-me.cabal b/debug-me.cabal
index a7d2b15..8e93598 100644
--- a/debug-me.cabal
+++ b/debug-me.cabal
@@ -11,7 +11,7 @@ Category: Utility
 Build-Type: Custom
 Synopsis: secure remote debugging
 Description:
- Debugging a problem over email is slow, tedious, and hard. The developer
+ Debugging a problem over email/irc/BTS is slow, tedious, and hard. The developer
  needs to see your problem to understand it. Debug-me aims to make debugging
  fast, fun, and easy, by letting the developer access your computer remotely,
  so they can immediately see and interact with the problem. Making your
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 0bb7cbe..84bc344 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -11,9 +11,9 @@
 
 [[!meta title="debug-me - secure remote debugging"]]
 
-Debugging a problem over email is slow, tedious, and hard. The developer
-needs to see the your problem to understand it. Debug-me aims to make
-debugging fast, fun, and easy, by letting the developer access your
+Debugging a problem over email/irc/BTS is slow, tedious, and hard. The
+developer needs to see the your problem to understand it. Debug-me aims to
+make debugging fast, fun, and easy, by letting the developer access your
 computer remotely, so they can immediately see and interact with the
 problem. Making your problem their problem gets it fixed fast.
 

add news item for debug-me 1.20170505
diff --git a/doc/news/version_1.20170505.mdwn b/doc/news/version_1.20170505.mdwn
new file mode 100644
index 0000000..221b369
--- /dev/null
+++ b/doc/news/version_1.20170505.mdwn
@@ -0,0 +1,3 @@
+debug-me 1.20170505 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+   * First release of debug-me."""]]
\ No newline at end of file

standalone tarball uploaded
diff --git a/doc/install.mdwn b/doc/install.mdwn
index f96c16c..2f8d24f 100644
--- a/doc/install.mdwn
+++ b/doc/install.mdwn
@@ -1,3 +1,14 @@
+## Linux: quick install
+
+[Download debug-me-standalone-amd64.tar.gz](https://downloads.kitenet.net/debug-me/linux/current/debug-me-standalone-amd64.tar.gz)
+
+To use:
+
+	tar xf debug-me-standalone-amd64.tar.gz
+	debug-me/debug-me
+
+## building from source
+
 Clone debug-me's git repository from <git://debug-me.branchable.com/>
 or <https://git.joeyh.name/git/debug-me.git>. Tags and commits are gpg
 signed, so can be verified.
diff --git a/doc/todo/packages.mdwn b/doc/todo/packages.mdwn
index 7c03108..5be5eb5 100644
--- a/doc/todo/packages.mdwn
+++ b/doc/todo/packages.mdwn
@@ -4,3 +4,5 @@ For any/all linux distros.
 
 Also, make a self-contained executable. (Statically linked would be ideal,
 but difficulty.)
+
+> [[done]]

add protocol version
This is distinct from the wire protocol version used in the websocket
framing of messages. Versioning the high level protocol will let later
features be added.
The user controls the protocol version, since they send the first
several messages. Developers that connect need to avoid using features
from newer protocol versions.
So, developers and servers will need to support the most recent version,
while the user can have an old version of debug-me and it will continue
to work.
This commit changes the protocol buffer encoding, and is the last such
free change. All changes past this point will need to be versioned.
This commit was sponsored by Jochen Bartl on Patreon.
diff --git a/ControlWindow.hs b/ControlWindow.hs
index 99fd4d3..2540640 100644
--- a/ControlWindow.hs
+++ b/ControlWindow.hs
@@ -118,7 +118,7 @@ displayInput ochan ichan promptchan responsechan = loop
 	go (Just (ControlWindowMessage m)) = do
 		putStrLn m
 		loop
-	go (Just (ControlInputAction (SessionKey k))) = do
+	go (Just (ControlInputAction (SessionKey k _))) = do
 		askToAllow ochan promptchan responsechan k
 		loop
 	go (Just (ControlInputAction m@(ChatMessage {}))) = do
diff --git a/Hash.hs b/Hash.hs
index 6fb5614..8a33803 100644
--- a/Hash.hs
+++ b/Hash.hs
@@ -53,7 +53,7 @@ instance Hashable Seen where
 instance Hashable ControlAction where
 	hash (EnteredRejected h1 h2) = hash $ Tagged "EnteredRejected"
 		[hash h1, hash h2]
-	hash (SessionKey pk) = hash $ Tagged "SessionKey" pk
+	hash (SessionKey pk v) = hash $ Tagged "SessionKey" [hash pk, hash v]
 	hash (SessionKeyAccepted pk) = hash $ Tagged "SessionKeyAccepted" pk
 	hash (SessionKeyRejected pk) = hash $ Tagged "SessionKeyRejected" pk
 	hash (ChatMessage u m) = hash $ Tagged "ChatMessage" [hash u, hash m]
diff --git a/ProtocolBuffers.hs b/ProtocolBuffers.hs
index 42d3e0b..e87a156 100644
--- a/ProtocolBuffers.hs
+++ b/ProtocolBuffers.hs
@@ -65,65 +65,67 @@ data ControlActionP
 		, enteredLastAcceptedP :: Optional 12 (Message HashP)
 		}
 	| SessionKeyP
-		{ sessionKeyP :: Required 13 (Message (PerhapsSignedP PublicKeyP)) }
+		{ sessionKeyP :: Required 13 (Message (PerhapsSignedP PublicKeyP))
+		, protocolVersionP :: Required 14 (Value B.ByteString)
+		}
 	| SessionKeyAcceptedP
-		{ sessionKeyAcceptedP :: Required 14 (Message PublicKeyP) }
+		{ sessionKeyAcceptedP :: Required 15 (Message PublicKeyP) }
 	| SessionKeyRejectedP
-		{ sessionKeyRejectedP :: Required 15 (Message PublicKeyP) }
+		{ sessionKeyRejectedP :: Required 16 (Message PublicKeyP) }
 	| ChatMessageP
-		{ chatMessageSenderName :: Required 16 (Value B.ByteString)
-		, chatMessage :: Required 17 (Value B.ByteString)
+		{ chatMessageSenderName :: Required 17 (Value B.ByteString)
+		, chatMessage :: Required 18 (Value B.ByteString)
 		}
 	deriving (Generic)
 
 data SignatureP
 	= Ed25519SignatureP
-		{ ed25519SignatureP :: Required 18 (Value B.ByteString) }
+		{ ed25519SignatureP :: Required 19 (Value B.ByteString) }
 	| OtherSignatureP
-		{ otherSignatureP :: Required 19 (Value B.ByteString) }
+		{ otherSignatureP :: Required 20 (Value B.ByteString) }
 	deriving (Generic)
 
 data PublicKeyP = PublicKeyP 
-	{ mkPublicKeyP :: Required 20 (Value B.ByteString) }
+	{ mkPublicKeyP :: Required 21 (Value B.ByteString) }
 	deriving (Generic)
 
 data PerhapsSignedP a
 	= GpgSignedP
-		{ gpgSignedValP :: Required 21 (Message a)
-		, gpgSigP :: Required 22 (Message GpgSigP)
-		, gpgKeyExportP :: Required 23 (Message GpgKeyExportP)
+		{ gpgSignedValP :: Required 22 (Message a)
+		, gpgSigP :: Required 23 (Message GpgSigP)
+		, gpgKeyExportP :: Required 24 (Message GpgKeyExportP)
 		}
 	| UnSignedP
-		{ mkUnSignedP :: Required 24 (Message a )
+		{ mkUnSignedP :: Required 25 (Message a )
 		}
 	deriving (Generic)
 
 data GpgSigP = GpgSigP
-	{ mkGpgSigP :: Required 25 (Value B.ByteString) }
+	{ mkGpgSigP :: Required 26 (Value B.ByteString) }
 	deriving (Generic)
 
 data GpgKeyExportP = GpgKeyExportP
-	{ mkGpgKeyExportP :: Required 26 (Value B.ByteString) }
+	{ mkGpgKeyExportP :: Required 27 (Value B.ByteString) }
 	deriving (Generic)
 
 data ElapsedTimeP = ElapsedTimeP
-	{ mkElapsedTimeP :: Required 27 (Value Double) }
+	{ mkElapsedTimeP :: Required 28 (Value Double) }
 	deriving (Generic)
 
 data AnyMessageP
-	= UserP { mkUserP :: Required 28 (Message (MessageP SeenP)) }
-	| DeveloperP { mkDeveloperP :: Required 29 (Message (MessageP EnteredP)) }
+	= UserP { mkUserP :: Required 29 (Message (MessageP SeenP)) }
+	| DeveloperP { mkDeveloperP :: Required 30 (Message (MessageP EnteredP)) }
 	deriving (Generic)
 
 data HashP = HashP
-	{ hashMethodP :: Required 30 (Message HashMethodP)
-	, hashValueP :: Required 31 (Value B.ByteString)
+	{ hashMethodP :: Required 31 (Message HashMethodP)
+	, hashValueP :: Required 32 (Value B.ByteString)
 	}
 	deriving (Generic)
 
 data HashMethodP
-	= SHA512P { mkSHA512P :: Required 32 (Value Bool) }
-	| SHA3P { mkSHA3P :: Required 33 (Value Bool) }
+	= SHA512P { mkSHA512P :: Required 33 (Value Bool) }
+	| SHA3P { mkSHA3P :: Required 34 (Value Bool) }
 	deriving (Generic)
 
 -- | Conversion between protocol buffer messages and debug-me's main Types.
@@ -190,8 +192,10 @@ instance ProtocolBuffer ControlActionP T.ControlAction where
 		{ enteredRejectedP = putField $ toProtocolBuffer $ T.enteredRejected t
 		, enteredLastAcceptedP = putField $ toProtocolBuffer <$> T.enteredLastAccepted t
 		}
-	toProtocolBuffer (T.SessionKey t) = SessionKeyP
-		{ sessionKeyP = putField $ toProtocolBuffer t }
+	toProtocolBuffer (T.SessionKey t v) = SessionKeyP
+		{ sessionKeyP = putField $ toProtocolBuffer t
+		, protocolVersionP = putField $ val v
+		}
 	toProtocolBuffer (T.SessionKeyAccepted t) = SessionKeyAcceptedP
 		{ sessionKeyAcceptedP = putField $ toProtocolBuffer t }
 	toProtocolBuffer (T.SessionKeyRejected t) = SessionKeyRejectedP
@@ -204,8 +208,9 @@ instance ProtocolBuffer ControlActionP T.ControlAction where
 		{ T.enteredRejected = fromProtocolBuffer $ getField $ enteredRejectedP p
 		, T.enteredLastAccepted = fromProtocolBuffer <$> getField (enteredLastAcceptedP p)
 		}
-	fromProtocolBuffer p@(SessionKeyP {}) = T.SessionKey $
-		fromProtocolBuffer $ getField $ sessionKeyP p
+	fromProtocolBuffer p@(SessionKeyP {}) = T.SessionKey
+		(fromProtocolBuffer $ getField $ sessionKeyP p)
+		(Val $ getField $ protocolVersionP p)
 	fromProtocolBuffer p@(SessionKeyAcceptedP {}) = T.SessionKeyAccepted $
 		fromProtocolBuffer $ getField $ sessionKeyAcceptedP p
 	fromProtocolBuffer p@(SessionKeyRejectedP {}) = T.SessionKeyRejected $
diff --git a/Role/Developer.hs b/Role/Developer.hs
index 2cdf917..d706a7a 100644
--- a/Role/Developer.hs
+++ b/Role/Developer.hs
@@ -226,7 +226,7 @@ authUser :: PerhapsSigned PublicKey -> TMChan (Message Entered) -> TMChan (Missi
 authUser spk ichan ochan devstate logger = do
 	ds <- atomically $ readTVar devstate
 	let msg = ControlMessage $ mkSigned (developerSessionKey ds) 
-		(Control (SessionKey spk))
+		(Control (SessionKey spk currentProtocolVersion))
 	atomically $ writeTMChan ichan msg
 	logger $ Developer msg
 	waitresp $ case spk of
@@ -291,7 +291,7 @@ getServerMessage ochan devstate ts = do
 				return (Just (o, User msg))
 			else return $ Just (ProtocolError ds $ "Bad signature on message from user: " ++ show msg, User msg)
 	-- When other developers connect, learn their SessionKeys.
-	process (Developer msg@(ControlMessage (Control (SessionKey spk) _))) = do
+	process (Developer msg@(ControlMessage (Control (SessionKey spk _) _))) = do
 		let sigverifier = mkSigVerifier $ case spk of
 			GpgSigned pk _ _ -> pk
 			UnSigned pk -> pk
@@ -334,7 +334,7 @@ getServerMessage ochan devstate ts = do
 			}
 		writeTVar devstate ds'
 		return Beep
-	processuser _ (ControlMessage (Control c@(SessionKey _) _)) =
+	processuser _ (ControlMessage (Control c@(SessionKey _ _) _)) =
 		return (GotControl c)
 	processuser _ (ControlMessage (Control c@(SessionKeyAccepted _) _)) =
 		return (GotControl c)
@@ -421,7 +421,7 @@ processSessionStart sk ochan logger dsv = do
 		<$> atomically (readTMChan ochan)
 	logger sessionmsg
 	sigverifier <- case sessionmsg of
-		User (ControlMessage c@(Control (SessionKey spk) _)) -> do
+		User (ControlMessage c@(Control (SessionKey spk _) _)) -> do
 			let pk = case spk of
 				GpgSigned k _ _ -> k
 				UnSigned k -> k
diff --git a/Role/User.hs b/Role/User.hs
index 6c6fb39..6ee1a42 100644
--- a/Role/User.hs
+++ b/Role/User.hs
@@ -118,7 +118,7 @@ startProtocol starttxt ochan logger = do
 		logger $ User msg
 	sk <- genMySessionKey
 	pk <- myPublicKey sk (GpgSign False)
-	let c = mkSigned sk $ Control (SessionKey pk)
+	let c = mkSigned sk $ Control (SessionKey pk currentProtocolVersion)
 	initialmessage $ ControlMessage c
 	let starttxt' = rawLine starttxt
 	let act = mkSigned sk $ Activity
@@ -248,7 +248,7 @@ getDeveloperMessage' (MissingHashes wiremsg) ochan us now = do
 	st <- readTVar us
 	Developer msg <- restoreHashes (userStateRecentActivity us) (MissingHashes (Developer wiremsg))

(Diff truncated)
use webms
firefox does not like the mp4s
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 4bbd48a..0bb7cbe 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -34,8 +34,8 @@ If the developer did do something bad, you'd have proof that they cannot
 be trusted, which you can share with the world. Knowing that is the case
 will keep most developers honest.
 
-<video controls width=400 title="debug-me demo" src="https://downloads.kitenet.net/videos/debug-me/debug-me-demo.mp4"></video>
-<video controls width=400 title="debug-me logs" src="https://downloads.kitenet.net/videos/debug-me/debug-me-logs.mp4"></video>
+<video controls width=400 title="debug-me demo" src="https://downloads.kitenet.net/videos/debug-me/debug-me-demo.webm"></video>
+<video controls width=400 title="debug-me logs" src="https://downloads.kitenet.net/videos/debug-me/debug-me-logs.webm"></video>
 
 ----
 

add screencasts
diff --git a/doc/index.mdwn b/doc/index.mdwn
index dcf763e..4bbd48a 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -34,6 +34,9 @@ If the developer did do something bad, you'd have proof that they cannot
 be trusted, which you can share with the world. Knowing that is the case
 will keep most developers honest.
 
+<video controls width=400 title="debug-me demo" src="https://downloads.kitenet.net/videos/debug-me/debug-me-demo.mp4"></video>
+<video controls width=400 title="debug-me logs" src="https://downloads.kitenet.net/videos/debug-me/debug-me-logs.mp4"></video>
+
 ----
 
 Debug-me is free software, created by [Joey Hess](https://joeyh.name/)

mention --verify
diff --git a/doc/evidence.mdwn b/doc/evidence.mdwn
index 1306316..05ab691 100644
--- a/doc/evidence.mdwn
+++ b/doc/evidence.mdwn
@@ -16,6 +16,10 @@ The important thing is that each message points to the SHA256 hash of a
 previous message, which builds up a chain. This chain can be
 verified by simply checking the hashes.
 
+Use `debug-me --verify` to verify a debug-me log file. It will display
+any GnuPG keys that signed session keys, and will verify all session key
+signatures, and all hashes.
+
 ## graphing debug-me sessions
 
 The chain of activities can be visualized by running `debug-me --graphviz

--verify mode
This commit was sponsored by Thom May on Patreon.
diff --git a/CmdLine.hs b/CmdLine.hs
index 0046b4c..2a64b6c 100644
--- a/CmdLine.hs
+++ b/CmdLine.hs
@@ -25,6 +25,7 @@ data Mode
 	| WatchMode WatchOpts
 	| GraphvizMode GraphvizOpts
 	| ReplayMode ReplayOpts
+	| VerifyMode VerifyOpts
 	| ServerMode ServerOpts
 	| ControlMode ControlOpts
 
@@ -54,6 +55,10 @@ data ReplayOpts = ReplayOpts
 	{ replayLogFile :: FilePath
 	}
 
+data VerifyOpts = VerifyOpts
+	{ verifyLogFile :: FilePath
+	}
+
 data ServerOpts = ServerOpts
 	{ serverDirectory :: FilePath
 	, serverPort :: Port
@@ -72,6 +77,7 @@ parseMode :: Parser Mode
 parseMode = (UserMode <$> parseuser)
 	<|> (DeveloperMode <$> parsedeveloper)
 	<|> (ReplayMode <$> parsereplay)
+	<|> (VerifyMode <$> parseverify)
 	<|> (DownloadMode <$> parsedownload)
 	<|> (WatchMode <$> parsewatch)
 	<|> (GraphvizMode <$> parsegraphviz)
@@ -112,6 +118,12 @@ parseMode = (UserMode <$> parseuser)
 			<> metavar "logfile"
 			<> help "replay log file"
 			)
+	parseverify = VerifyOpts
+		<$> option str
+			( long "verify"
+			<> metavar "logfile"
+			<> help "verify log file"
+			)
 	parsedownload = DownloadOpts
 		<$> option readurl
 			( long "download"
diff --git a/Crypto.hs b/Crypto.hs
index 8a3bd70..efc754f 100644
--- a/Crypto.hs
+++ b/Crypto.hs
@@ -44,6 +44,12 @@ instance Hashable t => Signed (Message t) where
 	hashExceptSignature (ActivityMessage a) = hashExceptSignature a
 	hashExceptSignature (ControlMessage c) = hashExceptSignature c
 
+instance Signed AnyMessage where
+	getSignature (User m) = getSignature m
+	getSignature (Developer m) = getSignature m
+	hashExceptSignature (User m) = hashExceptSignature m
+	hashExceptSignature (Developer m) = hashExceptSignature m
+
 sign :: Signed v => MySessionKey -> v -> Signature
 sign (MySessionKey sk pk) v = Ed25519Signature $ Val $ convert $
 	Ed25519.sign sk pk (toSign v)
diff --git a/debug-me.1 b/debug-me.1
index 242955b..bd1cfb0 100644
--- a/debug-me.1
+++ b/debug-me.1
@@ -72,6 +72,10 @@ Replay a debug-me log file with realistic pauses.
 While this is running, you can press Space to skip forward in the
 recording to the next point, which is useful when there are long pauses in
 the recording.
+.IP "--verify logfile"
+Verify that the log file contains a valid chain of hashes, and valid
+signatures. Will exit nonzero if any problem is detected. Displays the
+gpg public keys of any developers who interacted with the debug-me session.
 .IP "--graphviz logfile"
 Uses graphviz to generate a visualization of a debug-me log file.
 .IP "--show-hashes"
diff --git a/debug-me.hs b/debug-me.hs
index c9bbd22..42931c6 100644
--- a/debug-me.hs
+++ b/debug-me.hs
@@ -8,6 +8,7 @@ module Main where
 import CmdLine
 import Graphviz
 import Replay
+import Verify
 import Server
 import ControlWindow
 import qualified Role.User
@@ -28,5 +29,6 @@ main = withSocketsDo $ do
 		WatchMode o -> Role.Watcher.run o
 		GraphvizMode o -> graphviz o
 		ReplayMode o -> replay o
+		VerifyMode o -> verify o
 		ServerMode o -> server o
 		ControlMode o -> controlWindow o
diff --git a/doc/todo/log_file_analysis_mode.mdwn b/doc/todo/log_file_analysis_mode.mdwn
index 9520ae7..09ebccb 100644
--- a/doc/todo/log_file_analysis_mode.mdwn
+++ b/doc/todo/log_file_analysis_mode.mdwn
@@ -1,3 +1,5 @@
 Add a mode that, given a log file, displays what developer(s) gpg keys
 signed activity in the log file. For use when a developer did
 something wrong, to examine the proof.
+
+> [[done]]; --verify --[[Joey]]
diff --git a/doc/todo/verify_hash_chain_in_loadLog.mdwn b/doc/todo/verify_hash_chain_in_loadLog.mdwn
index 92f9741..c056e89 100644
--- a/doc/todo/verify_hash_chain_in_loadLog.mdwn
+++ b/doc/todo/verify_hash_chain_in_loadLog.mdwn
@@ -6,3 +6,5 @@ refuse to use logs that are not valid proofs of a session.
 Everything else in debug-me checks a session's proof as it goes.
 And, everything that saves a log file checks the proof as it goes,
 so perhaps this is not actually necessary?
+
+> Yeah, let's not. Instead, --verify can be used. [[done]] --[[Joey]]

debug-me server added
diff --git a/ServerList.hs b/ServerList.hs
index 685bc65..47e6723 100644
--- a/ServerList.hs
+++ b/ServerList.hs
@@ -10,4 +10,4 @@ import Data.Maybe
 
 defaultServerUrl :: URI
 defaultServerUrl = fromMaybe (error "internal url parse error") $
-	parseURI "http://localhost:8081/"
+	parseURI "http://debug-me.joeyh.name:8081/"
diff --git a/TODO b/TODO
deleted file mode 100644
index d2711a5..0000000
--- a/TODO
+++ /dev/null
@@ -1,5 +0,0 @@
-* Need to spin up a debug-me server and make debug-me use it by default,
-  not localhost.
-* Make debug-me --bundle create a self-contained executable bundle
-  that can be ran anywhere. Update faq to suggest developers include that
-  with their software.
diff --git a/debug-me.cabal b/debug-me.cabal
index 383d61d..e8e2503 100644
--- a/debug-me.cabal
+++ b/debug-me.cabal
@@ -36,7 +36,6 @@ License-File: AGPL
 Extra-Source-Files:
   CHANGELOG
   INSTALL
-  TODO
   Makefile
   debug-me.1
   debug-me.service
diff --git a/doc/todo/packages.mdwn b/doc/todo/packages.mdwn
new file mode 100644
index 0000000..7c03108
--- /dev/null
+++ b/doc/todo/packages.mdwn
@@ -0,0 +1,6 @@
+debug-me packages needed to make it easy for install.
+
+For any/all linux distros.
+
+Also, make a self-contained executable. (Statically linked would be ideal,
+but difficulty.)

update
diff --git a/doc/servers.mdwn b/doc/servers.mdwn
index c70e137..ed7176c 100644
--- a/doc/servers.mdwn
+++ b/doc/servers.mdwn
@@ -9,6 +9,11 @@ option, etc. See the man page for details.
 Your server needs to have a working mail transport agent so it can email
 logs to debug-me users.
 
+The debug-me source package includes an init script and a systemd service
+file. Running "make install" as root will install everything. Distribution
+packages of debug-me might put the server stuff in a separate package than
+the main debug-me package.
+
 debug-me has a server list built into it of servers it uses. To get your
 server added to the list, file a [[todo]] item with the url for your server,
 and be sure to say how long you anticipate the server running, where it's

links
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 25db3cd..dcf763e 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -17,17 +17,18 @@ debugging fast, fun, and easy, by letting the developer access your
 computer remotely, so they can immediately see and interact with the
 problem. Making your problem their problem gets it fixed fast.
 
-A debug-me session is logged and signed with the developer's GnuPG 
-key, producing a chain of evidence of what they saw and what they did. 
-So the developer's good reputation is leveraged to make debug-me secure.
+A debug-me session is logged and signed with the developer's GnuPG key,
+producing a [[chain of evidence|evidence]] of what they saw and what they
+did. So the developer's good reputation is leveraged to make debug-me
+secure.
 
 When you start debug-me without any options, it will connect to a debug-me
-server, and print out an url that you can give to the developer to get
-them connected to you. Then debug-me will show you their GnuPG key and who
-has signed it. If the developer has a good reputation, you can proceed
-to let them type into your console in a debug-me session. Once the
-session is done, the debug-me server will email you the signed
-evidence of what the developer did in the session.
+[[server|servers]], and print out an url that you can give to the developer
+to get them connected to you. Then debug-me will show you their GnuPG key
+and who has signed it. If the developer has a good reputation, you can
+proceed to let them type into your console in a debug-me session. Once the
+session is done, the debug-me server will email you the signed evidence of
+what the developer did in the session.
 
 If the developer did do something bad, you'd have proof that they cannot
 be trusted, which you can share with the world. Knowing that is the case

simplify
diff --git a/doc/evidence.mdwn b/doc/evidence.mdwn
index ae1590b..1306316 100644
--- a/doc/evidence.mdwn
+++ b/doc/evidence.mdwn
@@ -21,14 +21,13 @@ verified by simply checking the hashes.
 The chain of activities can be visualized by running `debug-me --graphviz
 logfile`.
 
-Here are two examples. In both of these, the shell started, the developer
-typed "echo hi" and then saw the output of the command, and then ended the
-session with control-d.
+Here's an example. Reading down the graph, you'll see the shell started,
+the developer typed "echo hi" and then saw the output of the command,
+and then ended the session with control-d.
 
-[[simple.png]] [[laggy.png]]
+[[laggy.png]]
 
-The difference between the two sessions is that the one 
-on the right went over a laggy network connection. In the middle of its
+That debug-me session had some lag. In the middle of its
 graph, you can see that the developer typed "h", and before that letter
 echoed back, went on to type "o ". Thus, the chain splits to reflect the
 different perspectives of the local and remote debug-me programs at that
diff --git a/doc/evidence/simple.png b/doc/evidence/simple.png
deleted file mode 100644
index dc37286..0000000
Binary files a/doc/evidence/simple.png and /dev/null differ

capitalization
diff --git a/ControlWindow.hs b/ControlWindow.hs
index e561017..5163a95 100644
--- a/ControlWindow.hs
+++ b/ControlWindow.hs
@@ -137,14 +137,14 @@ askToAllow ochan _ _ (UnSigned pk) = atomically $ writeTMChan ochan $
 	ControlOutputAction $ SessionKeyRejected pk
 askToAllow ochan promptchan responsechan k@(GpgSigned pk _) = do
 	putStrLn "Someone wants to connect to this debug-me session."
-	putStrLn "Checking their Gnupg signature ..."
+	putStrLn "Checking their GnuPG signature ..."
 	v <- gpgVerify [] k
 	case v of
 		Nothing -> do
-			putStrLn "Unable to download their Gnupg key, or signature verification failed."
+			putStrLn "Unable to download their GnuPG key, or signature verification failed."
 			reject
 		Just gpgkeyid -> flip catch woterror $ do
-			putStrLn "Checking the Gnupg web of trust ..."
+			putStrLn "Checking the GnuPG web of trust ..."
 			ss <- isInStrongSet gpgkeyid
 			ws <- downloadWotStats gpgkeyid
 			putStrLn $ describeWot ws ss
diff --git a/Role/Developer.hs b/Role/Developer.hs
index 51ada28..2cc6c1c 100644
--- a/Role/Developer.hs
+++ b/Role/Developer.hs
@@ -68,7 +68,7 @@ developer dsv ichan ochan sid = withSessionLogger (Just "remote") sid $ \logger
 			"(But, you can't type anything yet.)"
 		emitOutput startoutput
 		displayInControlWindow controlinput
-			"Waiting for the user to check your Gnupg key and grant write access ..."
+			"Waiting for the user to check your GnuPG key and grant write access ..."
 		authUser spk ichan ochan devstate logger
 			>>= go controlinput controloutput logger devstate
   where
diff --git a/debug-me.1 b/debug-me.1
index 8b39974..638f7d5 100644
--- a/debug-me.1
+++ b/debug-me.1
@@ -11,13 +11,13 @@ debugging fast, fun, and easy, by letting the developer access your
 computer remotely, so they can immediately see and interact with the
 problem. Making your problem their problem gets it fixed fast.
 .PP
-A debug-me session is logged and signed with the developer's Gnupg 
+A debug-me session is logged and signed with the developer's GnuPG 
 key, producing a chain of evidence of what they saw and what they did. 
 So the developer's good reputation is leveraged to make debug-me secure.
 .PP
 When you start debug-me without any options, it will connect to a debug-me
 server, and print out an url that you can give to the developer to get
-them connected to you. Then debug-me will show you their Gnupg key and who
+them connected to you. Then debug-me will show you their GnuPG key and who
 has signed it. If the developer has a good reputation, you can proceed
 to let them type into your console in a debug-me session. Once the
 session is done, the debug-me server will email you the signed
@@ -44,7 +44,7 @@ default server.
 .SH DEVELOPER OPTIONS
 .IP url
 Connect to a debug-me session on the specified url, to see and interact
-with the user's bug. You need a Gnupg key to use this.
+with the user's bug. You need a GnuPG key to use this.
 .IP "--watch url"
 Connect to a debug-me session on the specified url and display what
 happens in the session. Your keystrokes will not be sent to the session.
diff --git a/debug-me.cabal b/debug-me.cabal
index ffeefed..d3043a5 100644
--- a/debug-me.cabal
+++ b/debug-me.cabal
@@ -17,13 +17,13 @@ Description:
  so they can immediately see and interact with the problem. Making your
  problem their problem gets it fixed fast.
  .
- A debug-me session is logged and signed with the developer's Gnupg
+ A debug-me session is logged and signed with the developer's GnuPG
  key, producing a chain of evidence of what they saw and what they did.
  So the developer's good reputation is leveraged to make debug-me secure.
  .
  When you start debug-me without any options, it will connect to a debug-me
  server, and print out an url that you can give to the developer to get
- them connected to you. Then debug-me will show you their Gnupg key and who
+ them connected to you. Then debug-me will show you their GnuPG key and who
  has signed it. If the developer has a good reputation, you can proceed
  to let them type into your console in a debug-me session. Once the
  session is done, the debug-me server will email you the signed
diff --git a/doc/evidence.mdwn b/doc/evidence.mdwn
index 1b44ee0..ae1590b 100644
--- a/doc/evidence.mdwn
+++ b/doc/evidence.mdwn
@@ -3,10 +3,10 @@ It shows what the developer who connected to the server saw, and what the
 developer did.
 
 The log file uses an Ed25519 session key that is signed by the developer's
-Gnupg key, so everything the developer did in the session is effectively
-signed by their Gnupg key. It's impossible to generate a log file that
+GnuPG key, so everything the developer did in the session is effectively
+signed by their GnuPG key. It's impossible to generate a log file that
 shows a developer doing something other than what they did, unless you
-have the developer's Gnupg private key.
+have the developer's GnuPG private key.
 
 The log file is formatted as a series of JSON objects, and includes both
 messages from the user's debug-me, and from the developer's debug-me. See
diff --git a/doc/faq.mdwn b/doc/faq.mdwn
index 155dd5f..c9b46ea 100644
--- a/doc/faq.mdwn
+++ b/doc/faq.mdwn
@@ -7,23 +7,23 @@
 #### Should I let John Doe connect to my debug-me session? I don't know that guy.
 
 When a developer connects to your debug-me session, it will display
-their Gnupg key, and the number of people who have signed it. It will
+their GnuPG key, and the number of people who have signed it. It will
 also list the names of some of those people (the best connected ones).
 
 If the developer of software you use is connecting to debug-me,
-their software documentation might say what their Gnupg key is. Then you
-can simply check that the Gnupg key ids match.
+their software documentation might say what their GnuPG key is. Then you
+can simply check that the GnuPG key ids match.
 
 If debug-me says that "John Doe is probably a real person", it means
-that he's connected to the strong set of the Gnupg web of trust.
+that he's connected to the strong set of the GnuPG web of trust.
 Other people, who certianly are real, have verified his identity.
 So even if you don't know his name, it can be safe to let him connect.
 
 But it's a gut call. If in doubt, don't let the developer connect.
 
-If debug-me says "identity cannot be verified!", it means that the Gnupg
+If debug-me says "identity cannot be verified!", it means that the GnuPG
 key couldn't be downloaded at all, or the developer is not connected to the
-strong set of the Gnupg web of trust. Be super wary in this case.
+strong set of the GnuPG web of trust. Be super wary in this case.
 
 #### I don't feel comfortable letting someone run commands on my computer. Can I still use debug-me?
 
@@ -45,7 +45,7 @@ debug-me to let them type, there would be a proof of anything bad they did.
 #### How does debug-me prove when the developer uses it to do something bad?
 
 The debug-me session log file records everything the developer saw and did
-in a debug-me session. Each keystroke they sent is signed with their Gnupg
+in a debug-me session. Each keystroke they sent is signed with their GnuPG
 key, and is part of a signed chain of activities. By examining this
 evidence, it can be cryptographically proven that the developer did what
 they did.
@@ -60,11 +60,11 @@ make sure you get a copy.
 
 Here's a quick checklist:
 
-* Make a Gnupg key, if you don't already have one.
+* Make a GnuPG key, if you don't already have one.
 * Get it signed by at least one person who's connected to the strong
-  set of the Gnupg web of trust. The more signatures the better.
+  set of the GnuPG web of trust. The more signatures the better.
   Keysigning parties are great.
-* Include your Gnupg key id in your project's documentation, so users
+* Include your GnuPG key id in your project's documentation, so users
   will know which key is yours. It also helps to sign git tags,
   tarballs, git commits, etc with your key.
 * When a user has a bug that you need more information to reproduce and
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 1051a53..25db3cd 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -17,13 +17,13 @@ debugging fast, fun, and easy, by letting the developer access your
 computer remotely, so they can immediately see and interact with the
 problem. Making your problem their problem gets it fixed fast.
 
-A debug-me session is logged and signed with the developer's Gnupg 
+A debug-me session is logged and signed with the developer's GnuPG 
 key, producing a chain of evidence of what they saw and what they did. 
 So the developer's good reputation is leveraged to make debug-me secure.
 
 When you start debug-me without any options, it will connect to a debug-me
 server, and print out an url that you can give to the developer to get
-them connected to you. Then debug-me will show you their Gnupg key and who
+them connected to you. Then debug-me will show you their GnuPG key and who
 has signed it. If the developer has a good reputation, you can proceed
 to let them type into your console in a debug-me session. Once the
 session is done, the debug-me server will email you the signed

links
diff --git a/doc/evidence.mdwn b/doc/evidence.mdwn
index aebafd8..1b44ee0 100644
--- a/doc/evidence.mdwn
+++ b/doc/evidence.mdwn
@@ -25,7 +25,7 @@ Here are two examples. In both of these, the shell started, the developer
 typed "echo hi" and then saw the output of the command, and then ended the
 session with control-d.
 
-[[!simple.png]] [[!laggy.png]]
+[[simple.png]] [[laggy.png]]
 
 The difference between the two sessions is that the one 
 on the right went over a laggy network connection. In the middle of its

links
diff --git a/doc/evidence.mdwn b/doc/evidence.mdwn
index 8c5a309..aebafd8 100644
--- a/doc/evidence.mdwn
+++ b/doc/evidence.mdwn
@@ -25,7 +25,7 @@ Here are two examples. In both of these, the shell started, the developer
 typed "echo hi" and then saw the output of the command, and then ended the
 session with control-d.
 
-[[!simple]] [[!laggy]]
+[[!simple.png]] [[!laggy.png]]
 
 The difference between the two sessions is that the one 
 on the right went over a laggy network connection. In the middle of its

add page
diff --git a/doc/evidence.mdwn b/doc/evidence.mdwn
new file mode 100644
index 0000000..8c5a309
--- /dev/null
+++ b/doc/evidence.mdwn
@@ -0,0 +1,36 @@
+A debug-me log file is evidence of what happened in a debug-me session.
+It shows what the developer who connected to the server saw, and what the
+developer did.
+
+The log file uses an Ed25519 session key that is signed by the developer's
+Gnupg key, so everything the developer did in the session is effectively
+signed by their Gnupg key. It's impossible to generate a log file that
+shows a developer doing something other than what they did, unless you
+have the developer's Gnupg private key.
+
+The log file is formatted as a series of JSON objects, and includes both
+messages from the user's debug-me, and from the developer's debug-me. See
+[[protocol]] for more details about the messages.
+
+The important thing is that each message points to the SHA256 hash of a
+previous message, which builds up a chain. This chain can be
+verified by simply checking the hashes.
+
+## graphing debug-me sessions
+
+The chain of activities can be visualized by running `debug-me --graphviz
+logfile`.
+
+Here are two examples. In both of these, the shell started, the developer
+typed "echo hi" and then saw the output of the command, and then ended the
+session with control-d.
+
+[[!simple]] [[!laggy]]
+
+The difference between the two sessions is that the one 
+on the right went over a laggy network connection. In the middle of its
+graph, you can see that the developer typed "h", and before that letter
+echoed back, went on to type "o ". Thus, the chain splits to reflect the
+different perspectives of the local and remote debug-me programs at that
+point. Since they were able to agree on a resolution, the chain then 
+merges back together.
diff --git a/doc/evidence/laggy.png b/doc/evidence/laggy.png
new file mode 100644
index 0000000..6df0322
Binary files /dev/null and b/doc/evidence/laggy.png differ
diff --git a/doc/evidence/simple.png b/doc/evidence/simple.png
new file mode 100644
index 0000000..dc37286
Binary files /dev/null and b/doc/evidence/simple.png differ
diff --git a/doc/index.mdwn b/doc/index.mdwn
index f594425..1051a53 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -5,6 +5,7 @@
 * [[Bugs]]
 * [[Todo]]
 * [[Protocol]]
+* [[Evidence]]
 * [[Servers]]
 """]]
 

/quit
This commit was sponsored by Jake Vosloo on Patreon.
diff --git a/ControlSocket.hs b/ControlSocket.hs
index 2cf7d86..a53a2e7 100644
--- a/ControlSocket.hs
+++ b/ControlSocket.hs
@@ -32,6 +32,7 @@ data ControlInput
 data ControlOutput
 	= ControlOutputAction ControlAction
 	| ControlWindowOpened
+	| ControlWindowRequestedImmediateQuit
 	deriving (Show, Generic)
 
 instance ToJSON ControlInput
diff --git a/ControlWindow.hs b/ControlWindow.hs
index 3f050aa..e561017 100644
--- a/ControlWindow.hs
+++ b/ControlWindow.hs
@@ -25,8 +25,7 @@ import Control.Concurrent.Async
 import Control.Concurrent.STM
 import Control.Concurrent.STM.TMChan
 import qualified Data.ByteString as B
-import qualified Data.ByteString.Lazy as L
-import Data.ByteString.UTF8 (fromString, toString)
+import Data.ByteString.UTF8 (fromString)
 import Data.Char
 import Control.Monad
 import Data.Monoid
@@ -42,6 +41,7 @@ displayInControlWindow ichan msg = atomically $
 controlWindow :: ControlOpts -> IO ()
 controlWindow _ = do
 	putStrLn $ "** " ++ winDesc
+	putStrLn "(Enter /quit here at any time to end the debug-me session.)"
 	socketfile <- defaultSocketFile
 	ichan <- newTMChanIO
 	ochan <- newTMChanIO
@@ -80,7 +80,7 @@ openControlWindow = do
 	return (ichan, ochan)
 
 type Prompt = ()
-type Response = B.ByteString
+type Response = String
 
 type PromptChan = TChan Prompt
 type ResponseChan = TChan Response
@@ -88,21 +88,27 @@ type ResponseChan = TChan Response
 collectOutput :: TMChan ControlOutput -> PromptChan -> ResponseChan -> IO ()
 collectOutput ochan promptchan responsechan = do
 	myusername <- fromString <$> getLoginName
-	withLines stdin $ mapM_ $ processline myusername
+	loop myusername
   where
-	processline myusername l = do
-		mc <- atomically $ do
-			-- Is any particular input being prompted for now?
-			mp <- tryReadTChan promptchan
-			case mp of
-				Just _ -> do
-					writeTChan responsechan $ L.toStrict l
-					return Nothing
-				Nothing -> do
-					let c = ChatMessage (Val myusername) (Val $ L.toStrict l)
-					writeTMChan ochan $ ControlOutputAction c
-					return (Just c)
-		maybe (return ()) displayChatMessage mc
+	loop myusername = do
+		l <- getLine
+		if map toLower l == "/quit"
+			then atomically $
+				writeTMChan ochan ControlWindowRequestedImmediateQuit
+			else do
+				mc <- atomically $ do
+					-- Is any particular input being prompted for now?
+					mp <- tryReadTChan promptchan
+					case mp of
+						Just _ -> do
+							writeTChan responsechan l
+							return Nothing
+						Nothing -> do
+							let c = ChatMessage (Val myusername) (Val $ fromString l)
+							writeTMChan ochan $ ControlOutputAction c
+							return (Just c)
+				maybe (return ()) displayChatMessage mc
+				loop myusername
 
 displayInput :: TMChan ControlOutput -> TMChan ControlInput -> PromptChan -> ResponseChan -> IO ()
 displayInput ochan ichan promptchan responsechan = loop
@@ -150,7 +156,7 @@ askToAllow ochan promptchan responsechan k@(GpgSigned pk _) = do
 		putStr "Let them connect to the debug-me session and run commands? [y/n] "
 		hFlush stdout
 		r <- atomically $ readTChan responsechan
-		case map toLower (toString r) of
+		case map toLower r of
 			"y" -> accept
 			"yes" -> accept
 			"n" -> reject
diff --git a/Role/Developer.hs b/Role/Developer.hs
index d8d9d2c..51ada28 100644
--- a/Role/Developer.hs
+++ b/Role/Developer.hs
@@ -188,6 +188,7 @@ sendControlOutput controloutput ichan devstate logger = loop
 			return msg
 		logger (Developer msg)
 		loop
+	go (Just ControlWindowRequestedImmediateQuit) = return ()
 
 -- | Read activity from the TMChan and display it to the developer.
 --
diff --git a/Role/User.hs b/Role/User.hs
index a7e4843..49e9edf 100644
--- a/Role/User.hs
+++ b/Role/User.hs
@@ -65,8 +65,8 @@ run os = fromMaybe (ExitFailure 101) <$> connect
 			us <- startProtocol startSession ochan logger
 			atomically $ putTMVar usv us
 			workers <- mapM async
-				[ sendControlOutput controloutput ochan us logger
-				, sendPtyOutput p ochan us logger
+				[ sendPtyOutput p ochan us logger
+				, sendControlOutput controloutput ochan us logger ph
 				]
 			mainworker <- async $ sendPtyInput ichan ochan controlinput p us logger
 				`race` forwardTtyInputToPty p
@@ -350,8 +350,8 @@ isLegalEntered (Activity a (Just hp) lastentered _ _) us
 --
 -- When the control window sends a SessionKeyAccepted, add it to the
 -- sigVerifier.
-sendControlOutput :: TMChan ControlOutput -> TMChan (Message Seen) -> TVar UserState -> Logger -> IO ()
-sendControlOutput controloutput ochan us logger = loop
+sendControlOutput :: TMChan ControlOutput -> TMChan (Message Seen) -> TVar UserState -> Logger -> ProcessHandle -> IO ()
+sendControlOutput controloutput ochan us logger ph = loop
   where
 	loop = go =<< atomically (readTMChan controloutput)
 	go Nothing = return ()
@@ -369,3 +369,6 @@ sendControlOutput controloutput ochan us logger = loop
 		l <- atomically $ sendDeveloper ochan us c now
 		logger (User l)
 		loop
+	go (Just ControlWindowRequestedImmediateQuit) = do
+		terminateProcess ph
+		return ()
diff --git a/debug-me.1 b/debug-me.1
index 1bdd5fc..8b39974 100644
--- a/debug-me.1
+++ b/debug-me.1
@@ -28,9 +28,8 @@ running their buggy program in different ways, perhaps running a debugger,
 or looking at configuration files. They should *not* be looking at your
 personal files without asking you first in the debug-me chat window.
 They should not be downloading or installing other software. If you see
-them do anything you don't expect, press Control-S immediately, which
-will prevent them from doing anything else. You can also press
-Control-Backslash to immediately end the debug-me session.
+them do anything you don't expect, you can enter "/quit" in the control
+window to immediately end the debug-me session.
 .PP
 If the developer did do something bad, you'd have proof that they cannot
 be trusted, which you can share with the world. Knowing that is the case
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 7dafc20..f594425 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -28,15 +28,6 @@ to let them type into your console in a debug-me session. Once the
 session is done, the debug-me server will email you the signed
 evidence of what the developer did in the session.
 
-It's a good idea to watch the debug-me session. The developer should be
-running their buggy program in different ways, perhaps running a debugger,
-or looking at configuration files. They should *not* be looking at your
-personal files without asking you first in the debug-me chat window.
-They should not be downloading or installing other software. If you see
-them do anything you don't expect, press Control-S immediately, which
-will prevent them from doing anything else. You can also press
-Control-Backslash to immediately end the debug-me session.
-
 If the developer did do something bad, you'd have proof that they cannot
 be trusted, which you can share with the world. Knowing that is the case
 will keep most developers honest.

split out todo item
diff --git a/TODO b/TODO
index b048106..c7edc44 100644
--- a/TODO
+++ b/TODO
@@ -1,17 +1,3 @@
-* Client should upload to multiple servers, for redundancy. This way,
-  if Joey runs a server, and Alice runs a server, the user can start
-  debug-me and not worry that Joey will connect, do something bad, and have
-  his server cover it up, because Alice's server will also get the data.
-
-  When Bob connects to Alice's server and sends messages to the client,
-  it should then repeat those same messages to Joey's server (but not back
-  to Alice's server). 
-
-  This will use some more bandwidth of course. Inter-server replication
-  could also be done to avoid using client bandwidth. But then, if the
-  client only sent to Joey's server and trusted it to replicate to Alice,
-  Joey could break the replication to cover up his nefarious activities
-  in the debug-me session.
 * When the user presses control-s, before forwarding it to the terminal,
   stop accepting any developer input. Control-s again to resume.
   (Or, add buttons to the control window to do this.)
diff --git a/doc/todo/send_to_multiple_servers.mdwn b/doc/todo/send_to_multiple_servers.mdwn
new file mode 100644
index 0000000..94beb2a
--- /dev/null
+++ b/doc/todo/send_to_multiple_servers.mdwn
@@ -0,0 +1,15 @@
+Client should upload to multiple servers, for redundancy.
+
+This way, if Joey runs a server, and Alice runs a server, the user can
+start debug-me and not worry that Joey will connect, do something bad, and
+have his server cover it up, because Alice's server will also get the data.
+
+When Bob connects to Alice's server and sends messages to the client,
+it should then repeat those same messages to Joey's server (but not back
+to Alice's server). 
+
+This will use some more bandwidth of course. Inter-server replication
+could also be done to avoid using client bandwidth. But then, if the
+client only sent to Joey's server and trusted it to replicate to Alice,
+Joey could break the replication to cover up his nefarious activities
+in the debug-me session.

add prevEntered pointer
Client requires this always point to the previous Entered it accepted,
so a hash chain of Entered is built up, and there is no possibility for
ambiguity about which order a client received two Entered activies in.
So restoreHashes now has to try every possible combination of
known hashes for both prevEntered and prevActivity. That could be
significantly more work, but it would be unusual for there to be a lot
of known hashes, so it should be ok.
--graphviz shows this additional hash chain with grey edges
(and leaves out edges identical to the other hash chain)
While testing this with an artifical network lag, it turned out that
signature verification was failing for Reject messages sent by the
user. Didn't quite figure out what was at the bottom of that,
but the Activity Entered that was sent back in a Reject message was
clearly not useful, because it probably had both its prevEntered and
prevActivity hashes set to Nothing (because restoreHashes didn't restore
them, because the original Activity Entered was out of the expected
chain). So, switched Rejected to use a Hash.
(And renamed Rejected to EnteredRejected to make it more clear what
it's rejecting.)
Also, added a lastAccepted hash to EnteredRejected. This lets
the developer find its way back to the accepted chain when some
of its input gets rejected.
This commit was sponsored by Trenton Cronholm on Patreon.
diff --git a/Crypto.hs b/Crypto.hs
index d5273ae..8a3bd70 100644
--- a/Crypto.hs
+++ b/Crypto.hs
@@ -30,8 +30,8 @@ class Signed t where
 
 instance Hashable a => Signed (Activity a) where
 	getSignature = activitySignature
-	hashExceptSignature (Activity a mp mt _s) = hash $
-		Tagged "Activity" [hash a, hash mp, hash mt]
+	hashExceptSignature (Activity a mpa mpe mt _s) = hash $
+		Tagged "Activity" [hash a, hash mpa, hash mpe, hash mt]
 
 instance Signed Control where
 	getSignature = controlSignature
diff --git a/Graphviz.hs b/Graphviz.hs
index 59dba7f..f8f165c 100644
--- a/Graphviz.hs
+++ b/Graphviz.hs
@@ -8,7 +8,6 @@
 module Graphviz (graphviz) where
 
 import Types
-import Hash
 import CmdLine
 import Log
 
@@ -57,37 +56,37 @@ genGraph opts ls = digraph (Str "debug-me") $ do
 				, shape Circle
 				]
 			linkprev s a h
-		(User (ControlMessage c), Nothing) -> showcontrol c l
-		(Developer (ControlMessage c), Nothing) -> showcontrol c l
+		(User (ControlMessage c), Nothing) -> showcontrol c
+		(Developer (ControlMessage c), Nothing) -> showcontrol c
 		_ -> return ()
 
-	showcontrol (Control (Rejected ar) _) l = do
-		let hr = hash ar
+	showcontrol (Control (EnteredRejected hr _) _) = do
 		let rejstyle =
 			[ xcolor Red
 			, Style [dashed, filled]
 			]
-		let nodename = display $ "Rejected " <> display hr
+		let nodename = display $ "Rejected: " <> display hr
 		node nodename $ rejstyle ++
 			[ textLabel "Rejected"
 			, shape BoxShape
 			]
-		showactivity rejstyle $ Log
-			{ loggedMessage = Developer (ActivityMessage ar)
-			, loggedHash = Just hr
-			, loggedTimestamp = loggedTimestamp l
-			}
 		edge nodename (display hr) rejstyle
-	showcontrol _ _ = return ()
-
-	linkprev s a h = case prevActivity a of
-		Nothing -> return ()
-		Just p -> link p h s
+	showcontrol _ = return ()
+
+	linkprev s a h = do
+		case prevActivity a of
+			Nothing -> return ()
+			Just p -> link p h s
+		case prevEntered a of
+			Nothing -> return ()
+			Just p -> link p h (s ++ enteredpointerstyle)
 	link a b s = edge (display a) (display b) $ s ++
 		if graphvizShowHashes opts
 			then [ textLabel (prettyDisplay a) ]
 			else []
 
+	enteredpointerstyle = [ xcolor Gray ]
+
 xcolor :: X11Color -> Attribute
 xcolor c = Color [toWC $ X11Color c]
 
diff --git a/Hash.hs b/Hash.hs
index db4eae8..bf8e166 100644
--- a/Hash.hs
+++ b/Hash.hs
@@ -40,8 +40,8 @@ instance Hashable a => Hashable (Tagged a) where
 	hash (Tagged b a) = hash [hash b, hash a]
 
 instance Hashable a => Hashable (Activity a) where
-	hash (Activity a mp mt s) = hash $ Tagged "Activity"
-		[hash a, hash mp, hash mt, hash s]
+	hash (Activity a mps mpe mt s) = hash $ Tagged "Activity"
+		[hash a, hash mps, hash mpe, hash mt, hash s]
 
 instance Hashable Entered where
 	hash v = hash $ Tagged "Entered"
@@ -51,7 +51,8 @@ instance Hashable Seen where
 	hash v = hash $ Tagged "Seen" [hash (seenData v)]
 
 instance Hashable ControlAction where
-	hash (Rejected a) = hash $ Tagged "Rejected" a
+	hash (EnteredRejected h1 h2) = hash $ Tagged "EnteredRejected"
+		[hash h1, hash h2]
 	hash (SessionKey pk) = hash $ Tagged "SessionKey" pk
 	hash (SessionKeyAccepted pk) = hash $ Tagged "SessionKeyAccepted" pk
 	hash (SessionKeyRejected pk) = hash $ Tagged "SessionKeyRejected" pk
diff --git a/PrevActivity.hs b/PrevActivity.hs
index 0836c8b..74203fd 100644
--- a/PrevActivity.hs
+++ b/PrevActivity.hs
@@ -19,7 +19,10 @@ removeHashes msg = MissingHashes $ case msg of
 	Developer (ActivityMessage a) -> Developer (go a)
 	_ -> msg
   where
-	go a = ActivityMessage $ a { prevActivity = Nothing }
+	go a = ActivityMessage $ a
+		{ prevActivity = Nothing
+		, prevEntered = Nothing
+		}
 
 type RecentActivity = STM (SigVerifier, [Hash])
 
@@ -29,17 +32,27 @@ type RecentActivity = STM (SigVerifier, [Hash])
 -- point the message's signature will verify.
 restoreHashes :: RecentActivity -> MissingHashes AnyMessage -> STM AnyMessage
 restoreHashes ra (MissingHashes msg) = case msg of
-	User (ActivityMessage act) -> 
-		User . ActivityMessage <$> (go act =<< ra)
+	User (ActivityMessage act) ->
+		User . ActivityMessage <$> find act
 	Developer (ActivityMessage act) ->
-		Developer . ActivityMessage <$> (go act =<< ra)
+		Developer . ActivityMessage <$> find act
 	User (ControlMessage {}) -> return msg
 	Developer (ControlMessage {}) -> return msg
 
   where
-	go act (_, []) = return act
-	go act (sigverifier, (h:hs)) = do
-		let act' = act { prevActivity = Just h }
-		if verifySigned sigverifier act'
-			then return act'
-			else go act (sigverifier, hs)
+	find act = do
+		(sigverifier, l) <- ra
+		let l' = Nothing : map Just l
+		let ll = do
+			ah <- l'
+			eh <- l'
+			return $ act
+				{ prevActivity = ah
+				, prevEntered = eh
+				}
+		go act sigverifier ll
+	go act _ [] = return act
+	go act sigverifier (l:ls) = do
+		if verifySigned sigverifier l
+			then return l
+			else go act sigverifier ls
diff --git a/ProtocolBuffers.hs b/ProtocolBuffers.hs
index d5d6a0e..2d59528 100644
--- a/ProtocolBuffers.hs
+++ b/ProtocolBuffers.hs
@@ -48,9 +48,6 @@ data MessageP a
 
 data ActivityP a = ActivityP
 	{ activityP :: Required 6 (Message a)
-	-- This is not included, because the hash is never actually sent
-	-- over the wire!
-	-- , prevAtivityP :: Optional 7 (Message HashP)
 	, elapsedTimeP :: Required 8 (Message ElapsedTimeP)
 	, activitySignatureP :: Required 9 (Message SignatureP)
 	}
@@ -63,52 +60,65 @@ data ControlP = ControlP
 	deriving (Generic)
 
 data ControlActionP
-	= RejectedP
-		{ rejectedP :: Required 12 (Message (ActivityP EnteredP)) }
+	= EnteredRejectedP
+		{ enteredRejectedP :: Required 12 (Message HashP)
+		, enteredLastAcceptedP :: Optional 13 (Message HashP)
+		}
 	| SessionKeyP
-		{ sessionKeyP :: Required 13 (Message (PerhapsSignedP PublicKeyP)) }
+		{ sessionKeyP :: Required 14 (Message (PerhapsSignedP PublicKeyP)) }
 	| SessionKeyAcceptedP
-		{ sessionKeyAcceptedP :: Required 14 (Message PublicKeyP) }
+		{ sessionKeyAcceptedP :: Required 15 (Message PublicKeyP) }
 	| SessionKeyRejectedP
-		{ sessionKeyRejectedP :: Required 15 (Message PublicKeyP) }
+		{ sessionKeyRejectedP :: Required 16 (Message PublicKeyP) }
 	| ChatMessageP
-		{ chatMessageSenderName :: Required 16 (Value B.ByteString)
-		, chatMessage :: Required 17 (Value B.ByteString)
+		{ chatMessageSenderName :: Required 17 (Value B.ByteString)
+		, chatMessage :: Required 18 (Value B.ByteString)
 		}
 	deriving (Generic)
 
 data SignatureP

(Diff truncated)
links to sources
diff --git a/doc/protocol.mdwn b/doc/protocol.mdwn
index d290be7..5a0e679 100644
--- a/doc/protocol.mdwn
+++ b/doc/protocol.mdwn
@@ -3,10 +3,13 @@ the two participants, known as the user and the developer.
 
 The messages are serialized as JSON in debug-me log files, and protocol
 buffers are used when sending the messages over the wire. We won't go into
-the full details here. See Types.hs for the data types that JSON
-serialization instances are derived from, and ProocolBuffers.hs for the
-protocol buffers format. There is also a simple framing protocol used for
-communicating over websockets; see WebSockets.hs.
+the full details here. See
+[Types.hs](http://source.debug-me.branchable.com/?p=source.git;a=blob;f=Types.hs;hb=HEAD)
+for the data types that JSON serialization instances are derived from, and
+[ProocolBuffers.hs](http://source.debug-me.branchable.com/?p=source.git;a=blob;f=ProtocolBuffers.hs;hb=HEAD)
+for the protocol buffers format. There is also a simple framing protocol
+used for communicating over websockets; see
+[WebSockets.hs](http://source.debug-me.branchable.com/?p=source.git;a=blob;f=WebSockets.hs;hb=HEAD).
 
 The Activity type is the main message type. The user sends Activity
 Seen messages, and the developer responds with Activity Entered.
@@ -21,10 +24,11 @@ Activity Seen and Activity Entered messages have a prevActivity,
 which points to the Hash of a previous Activity. (And is Nothing for the
 first Activity Seen.) So a chain of messages is built up.
 
-(The exact details about how objects are hashed is not described here;
-see Hash.hs for the implementation. Note that the JSON strings are *not*
-directly hashed (to avoid tying hashing to JSON serialization details),
-instead the values in the data types are hashed.)
+(The exact details about how objects are hashed is not described here; see
+[Hash.hs](http://source.debug-me.branchable.com/?p=source.git;a=blob;f=Hash.hs;hb=HEAD)
+for the implementation. Note that the JSON strings are *not* directly
+hashed (to avoid tying hashing to JSON serialization details), instead the
+values in the data types are hashed.)
 
 The user and developer have different points of view. For example,
 the developer could send an Activity Entered at the same time the user

more
diff --git a/doc/todo/debug-me_log_to_ttyreq.mdwn b/doc/todo/debug-me_log_to_ttyreq.mdwn
new file mode 100644
index 0000000..f2955a2
--- /dev/null
+++ b/doc/todo/debug-me_log_to_ttyreq.mdwn
@@ -0,0 +1,2 @@
+Build a way to convert a debug-me log file to a ttyrec format. This way
+various tools that can eg, display ttyrecs on the web can be used.
diff --git a/doc/todo/pair_programming_uses_etc.mdwn b/doc/todo/pair_programming_uses_etc.mdwn
new file mode 100644
index 0000000..a121c65
--- /dev/null
+++ b/doc/todo/pair_programming_uses_etc.mdwn
@@ -0,0 +1,3 @@
+debug-me can be useful for more than debugging. It's straightforward enough
+to use it for pair programming, probably. Investigate such uses and
+document them.

rename
diff --git a/doc/todo/verify_hash_chain_in_loadLog.mdwn b/doc/todo/verify_hash_chain_in_loadLog.mdwn
new file mode 100644
index 0000000..92f9741
--- /dev/null
+++ b/doc/todo/verify_hash_chain_in_loadLog.mdwn
@@ -0,0 +1,8 @@
+loadLog should verify the hashes (and signatures) in the log, and
+refuse to use logs that are not valid proofs of a session.
+
+(--replay and --graphvis need this; server's use of loadLog does not)
+
+Everything else in debug-me checks a session's proof as it goes.
+And, everything that saves a log file checks the proof as it goes,
+so perhaps this is not actually necessary?
diff --git a/doc/todo/verify_hash_chain_in_loadLog.mdwna b/doc/todo/verify_hash_chain_in_loadLog.mdwna
deleted file mode 100644
index 92f9741..0000000
--- a/doc/todo/verify_hash_chain_in_loadLog.mdwna
+++ /dev/null
@@ -1,8 +0,0 @@
-loadLog should verify the hashes (and signatures) in the log, and
-refuse to use logs that are not valid proofs of a session.
-
-(--replay and --graphvis need this; server's use of loadLog does not)
-
-Everything else in debug-me checks a session's proof as it goes.
-And, everything that saves a log file checks the proof as it goes,
-so perhaps this is not actually necessary?

move low priority todos to web site
diff --git a/TODO b/TODO
index a932792..e3d5109 100644
--- a/TODO
+++ b/TODO
@@ -46,25 +46,3 @@
 * Make debug-me --bundle create a self-contained executable bundle
   that can be ran anywhere. Update faq to suggest developers include that
   with their software.
-
-Low priority:
-
-* Color the control window background to distinguish it from the shell
-  window. Could even use a curses toolkit to draw the control window, and
-  make it have buttons, etc. Make the control window easy to use, and all
-  features discoverable..
-* Add a mode that, given a log file, displays what developer(s) gpg keys
-  signed activity in the log file. For use when a developer did something
-  wrong, to examine the proof of malfesence.
-* loadLog should verify the hashes (and signatures) in the log, and
-  refuse to use logs that are not valid proofs of a session.
-  (--replay and --graphvis need this; server's use of loadLog does not)
-  Everything else in debug-me checks a session's proof as it goes.
-  And, everything that saves a log file checks the proof as it goes,
-  so perhaps this is not actually necessary?
-* GPG WoT is checked by querying pgp.cs.uu.nl, could use wotsap if it's
-  locally installed. However, the version of wotsap in debian only supports
-  short, insecure keyids, so is less secure than using the server.
-* Once we have a WoT path, we could download each gpg key in the path and
-  verify the path. This would avoid trusting pgp.cs.uu.nl not to be evil.
-  Not done yet, partly because downloading a lot of gpg keys is expensive.
diff --git a/doc/todo/better_control_window_UI.mdwn b/doc/todo/better_control_window_UI.mdwn
new file mode 100644
index 0000000..e44bd1b
--- /dev/null
+++ b/doc/todo/better_control_window_UI.mdwn
@@ -0,0 +1,4 @@
+Color the control window background to distinguish it from the shell
+window. Could even use a curses toolkit to draw the control window, and
+make it have buttons, etc. Make the control window easy to use, and all
+features discoverable..
diff --git a/doc/todo/decentralized_gpg_web_of_trust_checking.mdwn b/doc/todo/decentralized_gpg_web_of_trust_checking.mdwn
new file mode 100644
index 0000000..268fad1
--- /dev/null
+++ b/doc/todo/decentralized_gpg_web_of_trust_checking.mdwn
@@ -0,0 +1,15 @@
+GPG WoT is checked by querying pgp.cs.uu.nl, could use wotsap if it's
+locally installed. However, the version of wotsap in debian only supports
+short, insecure keyids, so is less secure than using the server.
+And, locally running wotsap needs to download the WoT database from
+a server anyway, so does not seem to add any security.
+
+Once we have a WoT path, we could download each gpg key in the path and
+verify the path. This would avoid trusting pgp.cs.uu.nl not to be evil.
+Not done yet, partly because downloading a lot of gpg keys is expensive.
+But also because even if this check were done, bad data in the WoT could
+be backed up by real keys on the keyservers.
+
+The decentralized way is for the user do some key signing, get into the WoT,
+and then gpg can tell them if the key is trusted itself. This
+already works of course.
diff --git a/doc/todo/log_file_analysis_mode.mdwn b/doc/todo/log_file_analysis_mode.mdwn
new file mode 100644
index 0000000..9520ae7
--- /dev/null
+++ b/doc/todo/log_file_analysis_mode.mdwn
@@ -0,0 +1,3 @@
+Add a mode that, given a log file, displays what developer(s) gpg keys
+signed activity in the log file. For use when a developer did
+something wrong, to examine the proof.
diff --git a/doc/todo/only_let_one_developer_type_at_a_time.mdwn b/doc/todo/only_let_one_developer_type_at_a_time.mdwn
new file mode 100644
index 0000000..0704bac
--- /dev/null
+++ b/doc/todo/only_let_one_developer_type_at_a_time.mdwn
@@ -0,0 +1,20 @@
+Two developers can connect to a session if the user accepts them both,
+and then they can type at the same time.
+
+debug-me will reject some keystrokes depending on what the other developer
+is doing. This is probably a bit confusing, and it could be a way to
+plausibly deny a bad action, making it look like it was caused by an
+inaverdant mix up of two developers typing.
+
+Better would be to only let one developer type at a time, and they have
+to pass the typing stick to let the other developer type.
+
+Problem: What if one developer has the baton and disconnects? It should
+pass to the other developer, but disconnection does not currently
+cause a protocol message.
+
+Also, related problem, one developer has the baton and goes away,
+still connected.
+
+Seems that perhaps the user needs a way to switch control to another
+developer.
diff --git a/doc/todo/soft_reject_developer_gpg_keys.mdwn b/doc/todo/soft_reject_developer_gpg_keys.mdwn
new file mode 100644
index 0000000..3e6c561
--- /dev/null
+++ b/doc/todo/soft_reject_developer_gpg_keys.mdwn
@@ -0,0 +1,7 @@
+When the user rejects a developer's gpg key, the developer can still
+watch the session, but they cannot chat in the control window. There seems
+no reason not to let them chat, it just needs a new response to tell
+them they have been soft rejected.
+
+Being able to put a developer in chat-only mode would also help
+with [[only_let_one_developer_type_at_a_time]].
diff --git a/doc/todo/verify_hash_chain_in_loadLog.mdwna b/doc/todo/verify_hash_chain_in_loadLog.mdwna
new file mode 100644
index 0000000..92f9741
--- /dev/null
+++ b/doc/todo/verify_hash_chain_in_loadLog.mdwna
@@ -0,0 +1,8 @@
+loadLog should verify the hashes (and signatures) in the log, and
+refuse to use logs that are not valid proofs of a session.
+
+(--replay and --graphvis need this; server's use of loadLog does not)
+
+Everything else in debug-me checks a session's proof as it goes.
+And, everything that saves a log file checks the proof as it goes,
+so perhaps this is not actually necessary?

update
diff --git a/doc/servers.mdwn b/doc/servers.mdwn
index dcd2482..c70e137 100644
--- a/doc/servers.mdwn
+++ b/doc/servers.mdwn
@@ -6,6 +6,9 @@ which you can run like this:
 You might also want to use the `--delete-old-logs` option, the `--port`
 option, etc. See the man page for details.
 
+Your server needs to have a working mail transport agent so it can email
+logs to debug-me users.
+
 debug-me has a server list built into it of servers it uses. To get your
 server added to the list, file a [[todo]] item with the url for your server,
 and be sure to say how long you anticipate the server running, where it's

update
diff --git a/doc/faq.mdwn b/doc/faq.mdwn
index 8eb1717..155dd5f 100644
--- a/doc/faq.mdwn
+++ b/doc/faq.mdwn
@@ -82,6 +82,9 @@ Never delete or modify their personal files.
 
 Don't download software. Don't try to sudo to root, etc.
 
+Don't share the log of your debug-me session without getting the user's
+approval, as it might contain information they wish to remain private.
+
 #### Seems some of my keystrokes are not getting through to the user?
 
 Due to the way debug-me's proof chains work ([[details|protocol]]), this

title
diff --git a/doc/faq.mdwn b/doc/faq.mdwn
index 79ca74a..8eb1717 100644
--- a/doc/faq.mdwn
+++ b/doc/faq.mdwn
@@ -1,3 +1,5 @@
+[[!meta title="debug-me Frequently Asked Questions"]]
+
 [[!toc levels=2]]
 
 ### For Users

caps
diff --git a/doc/faq.mdwn b/doc/faq.mdwn
index acf4018..79ca74a 100644
--- a/doc/faq.mdwn
+++ b/doc/faq.mdwn
@@ -1,6 +1,6 @@
 [[!toc levels=2]]
 
-### for users
+### For Users
 
 #### Should I let John Doe connect to my debug-me session? I don't know that guy.
 
@@ -52,7 +52,7 @@ For this to work, you have to have a copy of the session log file. This is
 why the debug-me server will email it to you at the end of the session, to
 make sure you get a copy.
 
-### for developers
+### For Developers
 
 #### What do I need to do to start using debug-me?
 

levels
diff --git a/doc/faq.mdwn b/doc/faq.mdwn
index b819332..acf4018 100644
--- a/doc/faq.mdwn
+++ b/doc/faq.mdwn
@@ -1,4 +1,4 @@
-[[!toc]]
+[[!toc levels=2]]
 
 ### for users
 

add
diff --git a/doc/faq.mdwn b/doc/faq.mdwn
new file mode 100644
index 0000000..b819332
--- /dev/null
+++ b/doc/faq.mdwn
@@ -0,0 +1,104 @@
+[[!toc]]
+
+### for users
+
+#### Should I let John Doe connect to my debug-me session? I don't know that guy.
+
+When a developer connects to your debug-me session, it will display
+their Gnupg key, and the number of people who have signed it. It will
+also list the names of some of those people (the best connected ones).
+
+If the developer of software you use is connecting to debug-me,
+their software documentation might say what their Gnupg key is. Then you
+can simply check that the Gnupg key ids match.
+
+If debug-me says that "John Doe is probably a real person", it means
+that he's connected to the strong set of the Gnupg web of trust.
+Other people, who certianly are real, have verified his identity.
+So even if you don't know his name, it can be safe to let him connect.
+
+But it's a gut call. If in doubt, don't let the developer connect.
+
+If debug-me says "identity cannot be verified!", it means that the Gnupg
+key couldn't be downloaded at all, or the developer is not connected to the
+strong set of the Gnupg web of trust. Be super wary in this case.
+
+#### I don't feel comfortable letting someone run commands on my computer. Can I still use debug-me?
+
+You can, just answer "n" when it asks if you want to let a developer
+run commands. The developer who connected to your debug-me session will
+still be able to see what you do in the session, so you can show them
+what they need to see to understand the bug.
+
+This is less efficient though. The developer will have to explain to you
+what to do, instead of just doing it. It will probably be harder for them
+to quickly get a grasp of the problem.
+
+Also, this can be less secure than letting the developer type stuff!
+Consider: The developer could ask you to run some complex command you've
+never heard of. Maybe that command will do something bad. Then there's
+no proof that the developer tricked you into doing that. If you use
+debug-me to let them type, there would be a proof of anything bad they did.
+
+#### How does debug-me prove when the developer uses it to do something bad?
+
+The debug-me session log file records everything the developer saw and did
+in a debug-me session. Each keystroke they sent is signed with their Gnupg
+key, and is part of a signed chain of activities. By examining this
+evidence, it can be cryptographically proven that the developer did what
+they did.
+
+For this to work, you have to have a copy of the session log file. This is
+why the debug-me server will email it to you at the end of the session, to
+make sure you get a copy.
+
+### for developers
+
+#### What do I need to do to start using debug-me?
+
+Here's a quick checklist:
+
+* Make a Gnupg key, if you don't already have one.
+* Get it signed by at least one person who's connected to the strong
+  set of the Gnupg web of trust. The more signatures the better.
+  Keysigning parties are great.
+* Include your Gnupg key id in your project's documentation, so users
+  will know which key is yours. It also helps to sign git tags,
+  tarballs, git commits, etc with your key.
+* When a user has a bug that you need more information to reproduce and
+  understand, ask if they'll use debug-me.
+
+#### What should I do and not do in a debug-me session?
+
+You're a guest in the user's computer, and your every move is being
+recorded as evidence. Act acordingly. Ask for permission, not forgiveness.
+
+Avoid looking at personal files unncessarily, and ask permission if you
+need to in order to investigate the bug.
+
+Never delete or modify their personal files. 
+
+Don't download software. Don't try to sudo to root, etc.
+
+#### Seems some of my keystrokes are not getting through to the user?
+
+Due to the way debug-me's proof chains work ([[details|protocol]]), this
+will sometimes happen, when you are typing faster than the roundtrip latency
+to the user. debug-me will beep when this happens (but a lot of modern
+terminal emulators don't sound beeps).
+
+This shouldn't be a problem, even at high latency, when you're typing
+at the shell prompt, but it may make using editors etc hard when latency
+is high.
+
+If a keystroke doesn't get though, wait a second and try it again,
+then it generally will.
+
+#### Any other terminal related problems I might encounter?
+
+If you're going to edit a file, etc it helps to have your terminal set to
+the same (or larger) number of lines and columns as the user's terminal.
+Run `echo $LINES $COLUMNS` in their shell to find the values there.
+
+It can sometimes be useful to `export TERM=vt100` in the user's shell
+to avoid any differences in terminal emulation.

faq
diff --git a/TODO b/TODO
index a80d893..04b0ad2 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,4 @@
+* Display developer gnupg key id on connect.
 * The current rules for when an Activity Entered is accepted allow it to
   refer to an older activity than the last one. If echoing is disabled,
   two Activity Entered could be sent, each pointing at the most recent
diff --git a/doc/index.mdwn b/doc/index.mdwn
index fed4481..7dafc20 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -1,10 +1,11 @@
 [[!sidebar content="""
-* [[install]]
-* [[news]]
-* [[bugs]]
-* [[todo]]
-* [[protocol]]
-* [[servers]]
+* [[Install]]
+* [[FAQ]]
+* [[News]]
+* [[Bugs]]
+* [[Todo]]
+* [[Protocol]]
+* [[Servers]]
 """]]
 
 [[!meta title="debug-me - secure remote debugging"]]

caps
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 6c62fe5..fed4481 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -43,4 +43,4 @@ will keep most developers honest.
 ----
 
 Debug-me is free software, created by [Joey Hess](https://joeyh.name/)
-and licensed under the terms of the GNU AGPL version 3 or greater.
+and licensed under the terms of the Gnu AGPL version 3 or greater.

move protocol to website
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 883a825..6c62fe5 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -3,6 +3,7 @@
 * [[news]]
 * [[bugs]]
 * [[todo]]
+* [[protocol]]
 * [[servers]]
 """]]
 
diff --git a/doc/protocol.mdwn b/doc/protocol.mdwn
new file mode 100644
index 0000000..d290be7
--- /dev/null
+++ b/doc/protocol.mdwn
@@ -0,0 +1,95 @@
+The debug-me protocol is a series of messages, exchanged between
+the two participants, known as the user and the developer. 
+
+The messages are serialized as JSON in debug-me log files, and protocol
+buffers are used when sending the messages over the wire. We won't go into
+the full details here. See Types.hs for the data types that JSON
+serialization instances are derived from, and ProocolBuffers.hs for the
+protocol buffers format. There is also a simple framing protocol used for
+communicating over websockets; see WebSockets.hs.
+
+The Activity type is the main message type. The user sends Activity
+Seen messages, and the developer responds with Activity Entered.
+There are also Control messages, which can be sent by either
+party at any time, and do not affect IO to the console.
+
+The first message in a debug-me session is a Control sent by the
+user, which establishes a session key (see below for details). The second
+message is an Activity Seen.
+
+Activity Seen and Activity Entered messages have a prevActivity,
+which points to the Hash of a previous Activity. (And is Nothing for the
+first Activity Seen.) So a chain of messages is built up.
+
+(The exact details about how objects are hashed is not described here;
+see Hash.hs for the implementation. Note that the JSON strings are *not*
+directly hashed (to avoid tying hashing to JSON serialization details),
+instead the values in the data types are hashed.)
+
+The user and developer have different points of view. For example,
+the developer could send an Activity Entered at the same time the user
+is sending an Activity Seen. It's not clear in which order these two
+Activities occurred -- in fact they occurred in different orders in
+different places -- and so the user and developer will disagree
+about it.
+
+Since the goal of debug-me is to produce a proof of the sequence of events
+that occurred in a session, that is a problem. Perhaps the developer was
+entering "y" in response to "Display detailed reactor logs?" at the same time
+that a new "Vent core to atmosphere?" question was being displayed!
+The debug-me protocol is designed to prevent such conflicts of opinion.
+
+The user only processes a new Activity Entered when it meets one of these
+requirements:
+
+1. The Activity Entered has as its prevActivity the last Activity
+   (Entered or Seen) that the user processed.
+2. The Activity Entered has as its prevActivity an older Activity
+   that the user processed, and its echoData matches the concacenation
+   of every Activity Seen after the prevActivity, up to the most recent
+   Activity Seen.
+
+   (This allows the developer to enter a command quickly without waiting
+   for each letter to echo back to them.)
+
+When an Activity Entered does not meet these rules, the user sends
+it back in a Rejected message to let the developer know the input was not
+allowed.
+
+The developer also checks the prevActivity of Activity Seen messages it
+receives from the user, to make sure that it's receiving a valid chain of
+messages. The developer accepts a new Activity Seen when either:
+
+1. The Activity Seen has a prevActivity that points to the last
+   Activity Seen that the developer accepted.
+2. The Activity Seen has as its prevActivity an Activity Entered
+   that the developer generated, after the last Activity Seen
+   that the developer accepted.
+
+At the start of the debug-me session, Ed25519 session key pairs are
+generated by both the user and the developer. The first message
+in the protocol is the user sending their session pubic key
+in a Control message containing a SessionKey.
+
+Before the developer can enter anything, they must send a SessionKey message
+with their session key, and it must be accepted by the user. The developer
+must have a gpg private key, which is used to sign their session key. 
+(The user may have a gpg private key, which may sign their session key
+if available, but this is optional.) The user will reject session keys
+that are not signed by a gpg key or when the gpg key is not one they
+trust. The user sends a SessionKeyAccepted/SessionKeyRejected control
+message to indicate if they accepted the developer's key or not.
+
+Each message in the debug-me session is signed by the party that sends it,
+using their session key. The hash of a message includes its signature, so
+the activity chain proves who sent a message, and who sent the message
+before it, etc.
+
+Note that there could be multiple developers, in which case each will
+send their session key before being able to do anything except observe
+the debug-me session.
+
+The prevActivity hash is actually not included in the data sent across the
+wire. It's left out to save space, and gets added back in by the receiver.
+The receiver uses the signature of the message to tell when it's found
+the right prevActivity hash to add back in.
diff --git a/protocol.txt b/protocol.txt
deleted file mode 100644
index d290be7..0000000
--- a/protocol.txt
+++ /dev/null
@@ -1,95 +0,0 @@
-The debug-me protocol is a series of messages, exchanged between
-the two participants, known as the user and the developer. 
-
-The messages are serialized as JSON in debug-me log files, and protocol
-buffers are used when sending the messages over the wire. We won't go into
-the full details here. See Types.hs for the data types that JSON
-serialization instances are derived from, and ProocolBuffers.hs for the
-protocol buffers format. There is also a simple framing protocol used for
-communicating over websockets; see WebSockets.hs.
-
-The Activity type is the main message type. The user sends Activity
-Seen messages, and the developer responds with Activity Entered.
-There are also Control messages, which can be sent by either
-party at any time, and do not affect IO to the console.
-
-The first message in a debug-me session is a Control sent by the
-user, which establishes a session key (see below for details). The second
-message is an Activity Seen.
-
-Activity Seen and Activity Entered messages have a prevActivity,
-which points to the Hash of a previous Activity. (And is Nothing for the
-first Activity Seen.) So a chain of messages is built up.
-
-(The exact details about how objects are hashed is not described here;
-see Hash.hs for the implementation. Note that the JSON strings are *not*
-directly hashed (to avoid tying hashing to JSON serialization details),
-instead the values in the data types are hashed.)
-
-The user and developer have different points of view. For example,
-the developer could send an Activity Entered at the same time the user
-is sending an Activity Seen. It's not clear in which order these two
-Activities occurred -- in fact they occurred in different orders in
-different places -- and so the user and developer will disagree
-about it.
-
-Since the goal of debug-me is to produce a proof of the sequence of events
-that occurred in a session, that is a problem. Perhaps the developer was
-entering "y" in response to "Display detailed reactor logs?" at the same time
-that a new "Vent core to atmosphere?" question was being displayed!
-The debug-me protocol is designed to prevent such conflicts of opinion.
-
-The user only processes a new Activity Entered when it meets one of these
-requirements:
-
-1. The Activity Entered has as its prevActivity the last Activity
-   (Entered or Seen) that the user processed.
-2. The Activity Entered has as its prevActivity an older Activity
-   that the user processed, and its echoData matches the concacenation
-   of every Activity Seen after the prevActivity, up to the most recent
-   Activity Seen.
-
-   (This allows the developer to enter a command quickly without waiting
-   for each letter to echo back to them.)
-
-When an Activity Entered does not meet these rules, the user sends
-it back in a Rejected message to let the developer know the input was not
-allowed.
-
-The developer also checks the prevActivity of Activity Seen messages it
-receives from the user, to make sure that it's receiving a valid chain of
-messages. The developer accepts a new Activity Seen when either:
-
-1. The Activity Seen has a prevActivity that points to the last
-   Activity Seen that the developer accepted.
-2. The Activity Seen has as its prevActivity an Activity Entered
-   that the developer generated, after the last Activity Seen
-   that the developer accepted.
-
-At the start of the debug-me session, Ed25519 session key pairs are
-generated by both the user and the developer. The first message
-in the protocol is the user sending their session pubic key
-in a Control message containing a SessionKey.
-
-Before the developer can enter anything, they must send a SessionKey message
-with their session key, and it must be accepted by the user. The developer
-must have a gpg private key, which is used to sign their session key. 
-(The user may have a gpg private key, which may sign their session key
-if available, but this is optional.) The user will reject session keys
-that are not signed by a gpg key or when the gpg key is not one they
-trust. The user sends a SessionKeyAccepted/SessionKeyRejected control
-message to indicate if they accepted the developer's key or not.

(Diff truncated)
more
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 41afca7..883a825 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -1,7 +1,9 @@
 [[!sidebar content="""
 * [[install]]
+* [[news]]
 * [[bugs]]
 * [[todo]]
+* [[servers]]
 """]]
 
 [[!meta title="debug-me - secure remote debugging"]]
diff --git a/doc/news.mdwn b/doc/news.mdwn
new file mode 100644
index 0000000..24957e8
--- /dev/null
+++ b/doc/news.mdwn
@@ -0,0 +1,2 @@
+[[!inline pages="news/* and !*/Discussion" show="3"]]
+
diff --git a/doc/servers.mdwn b/doc/servers.mdwn
new file mode 100644
index 0000000..dcd2482
--- /dev/null
+++ b/doc/servers.mdwn
@@ -0,0 +1,13 @@
+Want to run a debug-me server? debug-me includes a built-in server,
+which you can run like this:
+
+	debug-me --server /var/log/debug-me/ --from-email address something@example.com
+
+You might also want to use the `--delete-old-logs` option, the `--port`
+option, etc. See the man page for details.
+
+debug-me has a server list built into it of servers it uses. To get your
+server added to the list, file a [[todo]] item with the url for your server,
+and be sure to say how long you anticipate the server running, where it's
+located, and what kind of bandwidth it has available. The more debug-me
+servers, the better!

linkify
diff --git a/doc/install.mdwn b/doc/install.mdwn
index f8754d0..f96c16c 100644
--- a/doc/install.mdwn
+++ b/doc/install.mdwn
@@ -1,5 +1,5 @@
-Clone debug-me's git repository from `git://debug-me.branchable.com/`,
-or `https://git.joeyh.name/git/debug-me.git`. Tags and commits are gpg
+Clone debug-me's git repository from <git://debug-me.branchable.com/>
+or <https://git.joeyh.name/git/debug-me.git>. Tags and commits are gpg
 signed, so can be verified.
 
 To build from source, you need the 

sigs
diff --git a/doc/install.mdwn b/doc/install.mdwn
index 66a2010..f8754d0 100644
--- a/doc/install.mdwn
+++ b/doc/install.mdwn
@@ -1,5 +1,6 @@
 Clone debug-me's git repository from `git://debug-me.branchable.com/`,
-or `https://git.joeyh.name/git/debug-me.git`
+or `https://git.joeyh.name/git/debug-me.git`. Tags and commits are gpg
+signed, so can be verified.
 
 To build from source, you need the 
 [haskell tool stack](https://www.haskellstack.org/).

copy man page
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 9fdbdc7..41afca7 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -1,7 +1,43 @@
-todo!
-
 [[!sidebar content="""
 * [[install]]
 * [[bugs]]
 * [[todo]]
 """]]
+
+[[!meta title="debug-me - secure remote debugging"]]
+
+Debugging a problem over email is slow, tedious, and hard. The developer
+needs to see the your problem to understand it. Debug-me aims to make
+debugging fast, fun, and easy, by letting the developer access your
+computer remotely, so they can immediately see and interact with the
+problem. Making your problem their problem gets it fixed fast.
+
+A debug-me session is logged and signed with the developer's Gnupg 
+key, producing a chain of evidence of what they saw and what they did. 
+So the developer's good reputation is leveraged to make debug-me secure.
+
+When you start debug-me without any options, it will connect to a debug-me
+server, and print out an url that you can give to the developer to get
+them connected to you. Then debug-me will show you their Gnupg key and who
+has signed it. If the developer has a good reputation, you can proceed
+to let them type into your console in a debug-me session. Once the
+session is done, the debug-me server will email you the signed
+evidence of what the developer did in the session.
+
+It's a good idea to watch the debug-me session. The developer should be
+running their buggy program in different ways, perhaps running a debugger,
+or looking at configuration files. They should *not* be looking at your
+personal files without asking you first in the debug-me chat window.
+They should not be downloading or installing other software. If you see
+them do anything you don't expect, press Control-S immediately, which
+will prevent them from doing anything else. You can also press
+Control-Backslash to immediately end the debug-me session.
+
+If the developer did do something bad, you'd have proof that they cannot
+be trusted, which you can share with the world. Knowing that is the case
+will keep most developers honest.
+
+----
+
+Debug-me is free software, created by [Joey Hess](https://joeyh.name/)
+and licensed under the terms of the GNU AGPL version 3 or greater.

website start
diff --git a/doc/bugs.mdwn b/doc/bugs.mdwn
new file mode 100644
index 0000000..25c768e
--- /dev/null
+++ b/doc/bugs.mdwn
@@ -0,0 +1,4 @@
+This is debug-me's bug list. Link bugs to [[todo/done]] when done.
+
+[[!inline pages="./bugs/* and !./bugs/done and !link(done)
+and !*/Discussion" actions=yes postform=yes show=0 archive=yes]]
diff --git a/doc/bugs/.mdwn b/doc/bugs/.mdwn
new file mode 100644
index 0000000..c7af702
--- /dev/null
+++ b/doc/bugs/.mdwn
@@ -0,0 +1,3 @@
+fixed typo in README
+
+https://github.com/Yky/etckeeper/commit/5f2556abbc404eb0d06b31f620fe655c8802d8e7
diff --git a/doc/bugs/done.mdwn b/doc/bugs/done.mdwn
new file mode 100644
index 0000000..77a4306
--- /dev/null
+++ b/doc/bugs/done.mdwn
@@ -0,0 +1,4 @@
+recently fixed [[bugs]].
+
+[[!inline pages="./* and link(./done) and !*/Discussion" sort=mtime show=10
+archive=yes]]
diff --git a/doc/index.mdwn b/doc/index.mdwn
index d7616f7..9fdbdc7 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -1 +1,7 @@
 todo!
+
+[[!sidebar content="""
+* [[install]]
+* [[bugs]]
+* [[todo]]
+"""]]
diff --git a/doc/install.mdwn b/doc/install.mdwn
new file mode 100644
index 0000000..66a2010
--- /dev/null
+++ b/doc/install.mdwn
@@ -0,0 +1,10 @@
+Clone debug-me's git repository from `git://debug-me.branchable.com/`,
+or `https://git.joeyh.name/git/debug-me.git`
+
+To build from source, you need the 
+[haskell tool stack](https://www.haskellstack.org/).
+In the debug-me directory, run: `stack setup; stack install`
+
+Hopefully debug-me will be included in distributions to make it easy to
+install. If you have added debug-me to a distribution, edit this page to
+add instructions.
diff --git a/doc/todo.mdwn b/doc/todo.mdwn
new file mode 100644
index 0000000..44c6a77
--- /dev/null
+++ b/doc/todo.mdwn
@@ -0,0 +1,5 @@
+This is debug-me's todo list, for both posting feature requests, and merge
+requests. Link items to [[todo/done]] when done.
+
+[[!inline pages="./todo/* and !./todo/done and !link(done)
+and !*/Discussion" actions=yes postform=yes show=0 archive=yes]]
diff --git a/doc/todo/.mdwn b/doc/todo/.mdwn
new file mode 100644
index 0000000..c7af702
--- /dev/null
+++ b/doc/todo/.mdwn
@@ -0,0 +1,3 @@
+fixed typo in README
+
+https://github.com/Yky/etckeeper/commit/5f2556abbc404eb0d06b31f620fe655c8802d8e7
diff --git a/doc/todo/done.mdwn b/doc/todo/done.mdwn
new file mode 100644
index 0000000..e7c9808
--- /dev/null
+++ b/doc/todo/done.mdwn
@@ -0,0 +1,4 @@
+recently fixed [[todo]] items.
+
+[[!inline pages="./* and link(./done) and !*/Discussion" sort=mtime show=10
+archive=yes]]

testing
diff --git a/doc/index.mdwn b/doc/index.mdwn
index 258cd57..d7616f7 100644
--- a/doc/index.mdwn
+++ b/doc/index.mdwn
@@ -1 +1 @@
-todo
+todo!

add
diff --git a/doc/index.mdwn b/doc/index.mdwn
new file mode 100644
index 0000000..258cd57
--- /dev/null
+++ b/doc/index.mdwn
@@ -0,0 +1 @@
+todo