This page was last updated in 2021-01.

Note: We are no longer using monotone. The project has migrated all source repos to git.

This is a revised version of Complication's original guide detailing the use of Monotone in I2P development. For basic instructions see the quick-start guide.

I2P has a distributed development model. The source code is replicated across independently administered Monotone ("MTN") repositories. Developers with commit rights are able to push their changes to the repository (a license agreement needs to be signed before commit rights are granted).

Some of Monotone's noteworthy qualities are: distributed version control, cryptographic authentication, access control, its small size, having few dependencies, storage of projects in a compressed SQLite database file, and having the ability to resume interrupted synchronization attempts.

Operating a Monotone Client

Generating Monotone keys

A transport key grants you the ability to push your changes to a Monotone repository server. In order to commit code into Monotone (in essence signing your code), a commit key is also needed. None of the public Monotone servers on I2P currently require a key in order to read (or pull) the source code.

Without a transport key, one cannot:

  • pull code from a server which doesn't allow global read access
  • push code to any server
  • run a Monotone server

Without a commit key, one cannot:

  • commit any code

If you only intend to retrieve code from MTN, feel free to skip to the next section. If you want to generate keys, read the following.

By convention keys are named like an e-mail addresses, but a corresponding e-mail address does not need to exist. For example, your keys might be named:

  • yourname@mail.i2p
  • yourname-transport@mail.i2p

Monotone stores keys under $HOME/.monotone/keys in text files which are named identically to the keys. For example:

  • /home/complication/.monotone/keys/complication@mail.i2p

To generate transport and commit keys, enter the following commands at a prompt:

  • $ mtn genkey yourname-transport@someplace
  • $ mtn genkey yourname@someplace

Monotone will prompt you for a password to protect your keys. You are very strongly encouraged to set a password for the commit key. Many users will leave an empty password for the transport key, especially those running a Monotone server.

Trust, and initializing your repository

Monotone's security model helps to ensure that nobody can easily impersonate a developer without it being noticed. Since developers can make mistakes and become compromised,only manual review can ensure quality of code. Monotone's trust model will ensure that you read the right diffs. It does not replace reading diffs.

A Monotone repository is a single file (a compressed SQLite database) which contains all of the project's source code and history.

After importing the developers' keys into Monotone and setting up trust evaluation hooks, Monotone will prevent untrusted code from being checked out into your workspace. There are commands available to clean untrusted code from your workspace but in practice they've not been needed due to the push access policies in place.

A repository can hold many branches. For example, our repository holds the following main branches:

  • i2p.i2p — The I2P router and associated programs
  • i2p.www — The I2P project website
  • i2p.syndie — Syndie, a distributed forums tool

By convention, the I2P Monotone repository is named i2p.mtn. Before pulling source code from servers, a database for your repository will need to be initialized. To initialize your local repository, change into the directory that you want the i2p.mtn file and branch directories to be stored and issue the following command:

  • $ mtn --db="i2p.mtn" db init

Obtaining and deploying developers' keys

Keys which developers use to commit code are essential for trust evaluation in Monotone. The other developers' transport keys are only required for Monotone server operators.

Developers' commit keys are provided GPG-signed on another page.

To import developers' keys after verifying their authenticity, copy all of the keys into a new file. Create this file (e.g. keys.txt) in the same directory where i2p.mtn is located. Import the keys with the command:

  • $ mtn --db="i2p.mtn" read < keys.txt

Note: Never add keys to $HOME/.monotone/keys manually.

Setting up trust evaluation hooks

The default Monotone trust policy is way too lax for our requirements: every committer is trusted by default. That is not acceptable for I2P development.

Change into the directory $HOME/.monotone and open the file monotonerc with a text editor. Copy and paste the following two functions into this file:

-- This implements a list of trusted signers.
-- It is used on checkout and update.
-- It is not used for repo sync/push/pull.
-- If you do not include this function in ~/.monotone/monotonerc, the
-- default is to trust everybody, which is probably a bad thing
-- in an anonymous network.
-- Update the list below to reflect the signers YOU trust.
--
-- ref: http://www.monotone.ca/docs/Trust-Evaluation-Hooks.html
-- Modified to use key identities instead of key names, since
-- monotone allows duplicate key names, so any key-name-based
-- trust system is insecure.

--
--  Modified from intersection() to use key identities instead of key names, since
--  monotone allows duplicate key names.
--
--  a: table of ID structures (see above)
--  b: table of hex IDs
--
function keyintersection(a,b)
    local s={}
    local t={}
    for k,v in pairs(a) do s[v.id] = 1 end
    for k,v in pairs(b) do if s[v] ~= nil then table.insert(t,v) end end
    return t
end

--
-- from mtn source project.hh and lua_hooks.cc:
-- signers is a table of integers (starting with 1) to the following ID structure:
-- struct ID
-- {
--   id: (key_id in key_identity_info) hex of revision id hash;
--   given_name: (given_name in key_identity_info) // name given when creating the key
--   name: (official_name in key_identity_info) // name returned by hooks or (once implented) policy
-- };
-- id: hex of revision id hash;
-- name: cert_name
-- val: cert_value
--
function get_revision_cert_trust(signers, id, name, val)
   local trusted_signers = {
		"5bc185cfd680eb512fdb9626b9fb4298e136215e",	--  BlubMail@mail.i2p
		"f6706ac205e6b5d7a7e3ea4244ab0ef497f0a099",	--  cervantes@mail.i2p
		"690f278ff6c6157cbaf23b0d602b6d6dcf368313",	--  complication@mail.i2p
		"eb4ac08d5ddbb2bd73889f86c1211424025a6f07",	--  dev@robertfoss.se
		"aae785027c240ebbb0a883fd8ebcf8d6ecee4104",	--  dev@welterde.de
		"86478595288d1b96b58f0c8cd8a8971bc430f8fd",	--  dg2@mail.i2p
		-- completed dev agreement 2013-07 but never checked in anything
		--"5f75b8f0769770edc3267c21ddc9a00ddae31394",	--  digit@mail.i2p
		"4ebaace9973913416af92ee8d0fb93d64753df4c",	--  dream@mail.i2p
		"7e498ae94c9c322404adfc61b16bed388095906b",	--  duck@mail.i2p
		"6c728b0ffed3c2bf7fb0f3c583b30f966d9bacd5",	--  echelon2@mail.i2p
		"0e4e7ebebafbdf4cdacc45a47ba155b1215d8e8b",	--  forget@mail.i2p
		"f332b3d3b11b2efdae220cea75b9d5ba9ec3b52d",	--  hamada@mail.i2p
		"d681db14fd98da1efd6f8ceb2be6b91d784bdf5c",	--  hankhill19580@gmail.com
		"e246444b4fe69ba599e13403c4ab931066de902f",	--  hiddenz@mail.i2p
		"a61146ee69ddb9fcf3b82b19a62b8114b60d367e",	--  HungryHobo@mail.i2p
		"4844b1fd45f5a68744fa28d2f3e3b61a3cf83b95",	--  kytv@mail.i2p
		"6b2acfc9fe2f69b796631a514660fd7bdd237e2d",	--  laziestgravy@mail.i2p
		"c9b970f5e8917eada663d4c6b22096617021c95c",	--  m1xxy@mail.i2p
		"3be64909d6ab7c3d7afe16f20f24e672708b576b",	--  magma@mail.i2p
		"2977a6f4e11819a3f928783175caadc0071fc4de",	--  mathiasdm@mail.i2p
		"de9d196e8057e1629178edbfa1ed754c648d7340",	--  meeh@mail.i2p
		"2a0bba98558d7a9d7e4b1bd807789601252c0024",	--  mkvore-commit@mail.i2p
		"6ade4b7a9a6425194f482ab351950e4230dbbc85",	--  neutron@mail.i2p
		"bc74b49fd8a20513b2745a3d13414b7e9818dd18",	--  Oldaris@mail.i2p
		"3fb8d1ee1e82981a8076ddbcbf4d18f372b8bba7",	--  privateer@mail.i2p
		"e3815f0c985663182534fbd7d6a2bf93204a0bd0",	--  russiansponsor@mail.i2p
		"2ef1ae1e73a30e1afc0b4a7af89b4380b3dd46b7",	--  slumlord@mail.i2p
		"1092773c40f5813b9179d52a8ab7b499b9554da3",	--  sponge@mail.i2p
		"01265f0c817b24548478341fb75e672720a78b21",	--  str4d@mail.i2p
		"38fe2aa37e1eb9a300a2061ef153265c48031c6b",	--  walking@mail.i2p
		"a0eb78d437efad120dd9edcd776a327ec2c2adde",	--  zab@mail.i2p
		"2158706490e62a17c8140b6e9eabca965b681bc7",	--  zab2@mail.i2p
		"56810cd6434ab33593260e188b32bb83e4e9a139",	--  z3r0fox@mail.i2p
		"896e399990704373125f782ae2ee19b6611ac612"	--  zzz@mail.i2p
   }
   local t = keyintersection(signers, trusted_signers)
   if t == nil then return false end
   if #t>= 1 then return true end
   return false
end

The first function determines an intersection between two sets, in our case a revision's signers and trusted signers.

The second function determines trust in a given revision, by calling the first function with "signers" and "trusted" as arguments. If the intersection is null, the revision is not trusted. If the intersection is not empty, the revision is trusted. Otherwise, the revision is not trusted.

More information about Trust Evaluation Hooks can be found in the official Monotone documentation.

Pulling the i2p.i2p, i2p.www and i2p.syndie branches

I2P is shipped with a pre-configured tunnel pointing to the project Monotone server. Ensure that the tunnel has been started within I2PTunnel before attempting to pull the source code from 127.0.0.1:8998.

Enter the directory where you initialized i2p.mtn. Depending on whether you want only I2P sources, or also sources for the I2P website and Syndie, you can perform the pull operation in different ways.

If you only want I2P sources:

  • $ mtn --db="i2p.mtn" -k "" pull "mtn://127.0.0.1:8998?i2p.i2p"

If you want all branches:

  • $ mtn --db="i2p.mtn" -k "" pull "mtn://127.0.0.1:8998?i2p.*"
If the transfer aborts before completing sucessfully, simply repeating the pull command will resume the transfer.

Pulling in the above examples is done anonymously by specifying an empty transport key. If everyone pulls anonymously it will be harder for an attacker who gains control of the server to selectively provide some people with tampered data.

Verifying that trust evaluation works

To verify that trust evaluation works:

  • Make a backup of your monotonerc file.
  • Modify monotonerc by setting the trusted_signers variable in the following way:
           local trusted_signers = {}
      
  • With monotonerc configured as above, Monotone will no longer trust any committers. Confirm this by changing into the directory where i2p.mtn was created and attempt a checkout of the I2P branch:
    • $ mtn --db="i2p.mtn" co --branch="i2p.i2p"

    A directory named i2p.i2p should not appear. You should encounter many error messages like:

        mtn: warning: trust function disliked 1 signers
        of branch cert on revision 523c15f6f50cad3bb002f830111fc189732f693b
        mtn: warning: trust function disliked 1 signers
        of branch cert on revision 8ac13edc2919dbd5bb596ed9f203aa780bf23ff0
        mtn: warning: trust function disliked 1 signers
        of branch cert on revision 8c4dd8ad4053baabb102a01cd3f91278142a2cd1
        mtn: misuse: branch 'i2p.i2p' is empty
      

    If you are satisfied with results, restore the backup of monotonerc that was created above. If you didn't create a backup as advised, re-read Setting up trust evaluation hooks.

    Checking out a working copy of the latest version

    If you already have a branch checked out, skip to the next section.

    Change into the directory where i2p.mtn is located. Over there issue:

    • $ mtn --db="i2p.mtn" co --branch="i2p.i2p"

    The checkout should complete without error messages and a directory named i2p.i2p should appear in the current directory. Congratulations! You have successfully checked out the latest I2P sources, ready to be compiled.

    Updating your working copy to the latest version

    If you haven't done this already, pull fresh code from the server to your local Monotone repository. To accomplish this, change into the directory where i2p.mtn is located and issue:

    • $ mtn --db="i2p.mtn" -k "" pull "mtn://127.0.0.1:8998?i2p.i2p"

    Now change into your i2p.i2p directory, and over there issue:

    • $ mtn update

    As long as there were no errors…Congratulations! You have successfully updated to the latest I2P sources. They should be ready to compile.

    Operating a Monotone Server

    Obtaining and deploying developers' transport keys

    As a server operator you may want to grant push access to certain developers.

    Granting push and pull access

    By default the Monotone server denies all access.

    To grant pull access to all clients, set the following in $HOME/.monotone/read-permissions:

        pattern "*"
        allow "*"
    

    No one will not be able to push code to your server without permission being explicitly granted. To grant push access:

    • Add the name of the user's transport key to $HOME/.monotone/write-permissions, such as
          zzz-transport@mail.i2p
          complication-transport@mail.i2p
      
      with one key per line.
    • Import the transport key(s) into your database. The procedure for importing transport keys is the same as for importing commit keys, which is described in the section Obtaining and deploying developers' keys.

    Running Monotone in server mode

    A separate database should be used for your Monotone server because monotone will lock the database while it is served to others. Make a copy of your development database, then start the server with:

    • $ mtn serve --bind="127.0.0.1:8998" --db="i2p.mtn" --key "myserver-transport@mail.i2p"
    If your key is protected with a passphrase, Monotone may request the passphrase when the first client connects. You can work around this by connecting making the first client connection to your server (or by clearing the password for your transport key).

    For your server to be accessible for others over I2P, you will need to create a server tunnel for it. Use the "Standard" tunnel type and "Bulk" profile.

    Differences under Debian GNU/Linux

    Debian (amongst other distributions) has integrated Monotone into their framework of daemons/services. Although Monotone servers can still be run "the ordinary way" on Debian systems, doing it the "Debian way" may be more straightforward.

    Permissions are granted by editing the files /etc/monotone/read-permissions and /etc/monotone/write-permissions. You'll also need to edit /etc/default/monotone to enable monotone to start at boot or to customize the host, port, or database location.