Return-Path: X-Original-To: apmail-couchdb-dev-archive@www.apache.org Delivered-To: apmail-couchdb-dev-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D56E710611 for ; Sun, 4 Aug 2013 17:40:26 +0000 (UTC) Received: (qmail 95423 invoked by uid 500); 4 Aug 2013 17:40:26 -0000 Delivered-To: apmail-couchdb-dev-archive@couchdb.apache.org Received: (qmail 95330 invoked by uid 500); 4 Aug 2013 17:40:25 -0000 Mailing-List: contact dev-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list dev@couchdb.apache.org Received: (qmail 95318 invoked by uid 99); 4 Aug 2013 17:40:24 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 04 Aug 2013 17:40:24 +0000 X-ASF-Spam-Status: No, hits=-0.7 required=5.0 tests=RCVD_IN_DNSWL_LOW X-Spam-Check-By: apache.org Received-SPF: error (nike.apache.org: local policy) Received: from [80.244.253.218] (HELO mail.traeumt.net) (80.244.253.218) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 04 Aug 2013 17:40:18 +0000 Received: from [192.168.2.121] (p5B158BAB.dip0.t-ipconnect.de [91.21.139.171]) (using TLSv1 with cipher AES128-SHA (128/128 bits)) (No client certificate requested) by mail.traeumt.net (Postfix) with ESMTPSA id C864D1406E for ; Sun, 4 Aug 2013 19:41:43 +0200 (CEST) Subject: Re: Minimally Viable Plugins References: <9BF26E7B-D6D4-4D1F-A11B-771038B7FACB@apache.org> <6955D445-3D8A-482B-8F4F-12F94B1B12D0@apache.org> <3A847F88-3E70-4463-9E91-383290D871F5@apache.org> <3754CC5CF2D84A32B807CB0E681AFDE4@cloudant.com> <20E0B52F-5B43-4E16-AAB6-3DA495F6261B@apache.org> <51FE733F.9050600@gmail.com> <51FE7B63.9030405@gmail.com> From: Jan Lehnardt Content-Type: text/plain; charset=utf-8 X-Mailer: iPhone Mail (10B350) In-Reply-To: <51FE7B63.9030405@gmail.com> Message-Id: Date: Sun, 4 Aug 2013 19:39:36 +0200 To: "dev@couchdb.apache.org" Content-Transfer-Encoding: quoted-printable Mime-Version: 1.0 (1.0) X-Virus-Checked: Checked by ClamAV on apache.org On 04.08.2013, at 18:03, Octavian Damiean wrote: > -----BEGIN PGP SIGNED MESSAGE----- > Hash: SHA1 >=20 > Oh yea, forgot to tell. Done. :) Merged, thanks! >=20 > On 2013-08-04 17:39, Jan Lehnardt wrote: >>=20 >> On Aug 4, 2013, at 17:29 , Octavian Damiean >> wrote: >>=20 >>> Signed PGP part Great work! >>>=20 >>> Got a quick question. In the notice you mention that the >>> instructions work only with the 1867-feature-plugin branch but >>> you didn't add steps to checkout said branch in the preparation >>> step. >>>=20 >>> Want me to add that bit of information or was that deliberate? >>=20 >> Total oversight, you are more then welcome to add this :) >>=20 >> Jan -- >>=20 >>=20 >>=20 >>>=20 >>> Cheers, Octavian >>>=20 >>> On 2013-08-03 22:15, Jan Lehnardt wrote: >>>> More update: >>>>=20 >>>> I started producing a plugin template repo that people can >>>> clone to build their own plugins along with a comprehensive >>>> README.md: >>>>=20 >>>> https://github.com/janl/my-first-couchdb-plugin >>>>=20 >>>> The idea is to move the README to the CouchDB docs eventually >>>> and ship the plugin template with CouchDB, so people can get >>>> started easily. >>>>=20 >>>> Best Jan -- >>>>=20 >>>> On Aug 3, 2013, at 17:53 , Simon Metson =20 >>>> wrote: >>>>=20 >>>>> :) >>>>>=20 >>>>>=20 >>>>> On Saturday, 3 August 2013 at 14:21, Jan Lehnardt wrote: >>>>>=20 >>>>>> Couldn=E2=80=99t help but implement it. It=E2=80=99s in the branch no= w. >>>>>>=20 >>>>>> Jan -- >>>>>>=20 >>>>>> On Aug 3, 2013, at 08:12 , Simon Metson >>>>>> wrote: >>>>>>=20 >>>>>>> Sounds good to me. >>>>>>>=20 >>>>>>>=20 >>>>>>> On Saturday, 3 August 2013 at 00:56, Jan Lehnardt wrote: >>>>>>>=20 >>>>>>>>=20 >>>>>>>> On Aug 3, 2013, at 00:02 , Russell Branca=20 >>>>>>>> =20 >>>>>>>> wrote: >>>>>>>>=20 >>>>>>>>> This is fantastic, Jan! Glad to see this coming >>>>>>>>> along. >>>>>>>>>=20 >>>>>>>>> One of the goals with Fauxton has always been to make >>>>>>>>> it easy for plugins to extend the interface and >>>>>>>>> provide new functionality. I've been toying with the >>>>>>>>> idea of having a _fauxton db that plugins install to >>>>>>>>> as docs with attachments, but that's for a different >>>>>>>>> thread. The general idea here is that a plugin will >>>>>>>>> be able to extend Fauxton by adding a new page with >>>>>>>>> it's own functionality, or hook into existing pages >>>>>>>>> to extend other areas. >>>>>>>>>=20 >>>>>>>>> For instance, you could have a couchdb-lucene plugin >>>>>>>>> that hooks into the databases list and allows you to >>>>>>>>> add interfaces for building full text indexes and >>>>>>>>> searching on existing indexes. Or you could have a >>>>>>>>> dedicated page for Geocouch, or whatever. >>>>>>>>>=20 >>>>>>>>> The functionality is there, but it's still a bit of >>>>>>>>> a manual process, so we'll need to make it more >>>>>>>>> dynamic and smooth out the rough edges. >>>>>>>>>=20 >>>>>>>>> I'm very excited to see progress being made on >>>>>>>>> plugins, great work! >>>>>>>>=20 >>>>>>>> Thanks, I=E2=80=99m glad you like this! :) >>>>>>>>=20 >>>>>>>> Another way to get the Fauxton plugin loaded would be >>>>>>>> to extend the /_plugins API endpoint, so Fauxton could >>>>>>>> request GET /_plugins// and it would serve=20 >>>>>>>> /plugins/>>>>>>> just a place for Fauxton-enabled plugins. >>>>>>>>=20 >>>>>>>> Fauxton would walk /_config/plugins/ to get to a list >>>>>>>> of plugins. >>>>>>>>=20 >>>>>>>> In fact that should be pretty simple to set up. >>>>>>>>=20 >>>>>>>> For now I am trying to avoid having a custom database >>>>>>>> for this, mostly because I don=E2=80=99t think there are many=20 >>>>>>>> advantages (e.g. replication of plugins?) and code=20 >>>>>>>> complexity. These priorities might change in the >>>>>>>> future, but for now I am happy to get this working at >>>>>>>> all :) >>>>>>>>=20 >>>>>>>> If you are okay with the above plan of serving plugin=20 >>>>>>>> HTML/JS/CSS from /_plugins/, I=E2=80=99m happy to >>>>>>>> add this to the branch. >>>>>>>>=20 >>>>>>>> Best Jan -- >>>>>>>>=20 >>>>>>>>=20 >>>>>>>>=20 >>>>>>>>>=20 >>>>>>>>>=20 >>>>>>>>> -Russell >>>>>>>>>=20 >>>>>>>>>=20 >>>>>>>>> On Fri, Aug 2, 2013 at 2:17 PM, Jan Lehnardt=20 >>>>>>>>> wrote: >>>>>>>>>=20 >>>>>>>>>> And a few more (from COUCHDB-1867): >>>>>>>>>>=20 >>>>>>>>>> - Add uninstall, incl. Futon UI. - Only install a=20 >>>>>>>>>> plugin if the source and target CouchDB version=20 >>>>>>>>>> matches. - Rebase against master. >>>>>>>>>>=20 >>>>>>>>>> * * * >>>>>>>>>>=20 >>>>>>>>>> This concludes my list for a Minimally Viable >>>>>>>>>> Plugin feature. (See the original email or >>>>>>>>>> README.md (http://README.md)* for the roadmap) >>>>>>>>>>=20 >>>>>>>>>> I=E2=80=99d appreciate some more reviews & feedback**, but=20 >>>>>>>>>> other than that, I=E2=80=99d be happy to ship this as an=20 >>>>>>>>>> experimental feature in any next release. >>>>>>>>>>=20 >>>>>>>>>> *=20 >>>>>>>>>> https://github.com/janl/couchdb/blob/1867-feature-plugins/src/cou= ch_plugins/README.md#roadmap >>>=20 > ** >>>>>>>>>> https://github.com/janl/couchdb/compare/apache:master...1867-feat= ure-plugins >>>=20 > Best >>>>>>>>>> Jan -- >>>>>>>>>>=20 >>>>>>>>>> On Aug 1, 2013, at 19:34 , Jan Lehnardt >>>>>>>>>> wrote: >>>>>>>>>>=20 >>>>>>>>>>> A few updates: >>>>>>>>>>>=20 >>>>>>>>>>> By Bob Ippolito / @etrepum: - Plugins are now=20 >>>>>>>>>>> installed in libdir (instead of /tmp). - Config=20 >>>>>>>>>>> loading is now done with proper .ini files. - >>>>>>>>>>> Various cleanups and code review (Thanks!). >>>>>>>>>>>=20 >>>>>>>>>>> Mine (most suggested by Bob): - `plugins.html` >>>>>>>>>>> now shows you if a plugin is already installed. >>>>>>>>>>> and which version, if it doesn=E2=80=99t match the >>>>>>>>>>> installable one. - The Install button now >>>>>>>>>>> disables after an installation. - Plugins are now >>>>>>>>>>> registered with couch_config as >>>>>>>>>>> /_config/plugins/name =3D version - Updated >>>>>>>>>>> `couch-config` to print --erlang-version and=20 >>>>>>>>>>> --erl-bin - Updated the geocouch plugin to use >>>>>>>>>>> the new options in `couch-config`. - Added Bob >>>>>>>>>>> Ippolito=E2=80=99s couchperuser plugin to Futon. >>>>>>>>>>>=20 >>>>>>>>>>>=20 >>>>>>>>>>> Best Jan -- >>>>>>>>>>>=20 >>>>>>>>>>>=20 >>>>>>>>>>>=20 >>>>>>>>>>> On Jul 31, 2013, at 19:07 , Jan Lehnardt=20 >>>>>>>>>>> wrote: >>>>>>>>>>>=20 >>>>>>>>>>>> Heya, >>>>>>>>>>>>=20 >>>>>>>>>>>> I couldn=E2=80=99t help myself thinking about plugin >>>>>>>>>>>> stuff and ended up whipping up a proof of >>>>>>>>>>>> concept. >>>>>>>>>>>>=20 >>>>>>>>>>>> Here=E2=80=99s a <1 minute demo video: >>>>>>>>>>>>=20 >>>>>>>>>>>> https://dl.dropboxusercontent.com/u/82149/couchdb-plugins-demo.= mov >>>=20 > Alternative encoding: >>>>>>>>>>>>=20 >>>>>>>>>>>> https://dl.dropboxusercontent.com/u/82149/couchdb-plugins-demo.= m4v) >>>=20 > In my head the whole plugin idea is a very wide area, but I was so >>>>>>>>>>>> intrigued by the idea of getting something >>>>>>>>>>>> running with a click on a button in Futon. So I >>>>>>>>>>>> looked for a minimally viable plugin system. >>>>>>>>>>>>=20 >>>>>>>>>>>>=20 >>>>>>>>>>>> ## Design principles >>>>>>>>>>>>=20 >>>>>>>>>>>> It took me a day to put this all together and >>>>>>>>>>>> this was only possible because I took a lot of=20 >>>>>>>>>>>> shortcuts. I believe they are all viable for a >>>>>>>>>>>> first iteration of a plugins system: >>>>>>>>>>>>=20 >>>>>>>>>>>> 1. Install with one click on a button in Futon >>>>>>>>>>>> (or HTTP call) 2. Only pure Erlang plugins are=20 >>>>>>>>>>>> allowed. 3. The plugin author must provide a >>>>>>>>>>>> binary package for each Erlang (and, later, >>>>>>>>>>>> each CouchDB version). 4. Complete trust-based >>>>>>>>>>>> system. You trust me to not do any nasty things >>>>>>>>>>>> when you click on the install button. No >>>>>>>>>>>> crypto, no nothing. Only people who can commit >>>>>>>>>>>> to Futon can release new versions of plugins. >>>>>>>>>>>> 5. Minimal user-friendlyness: won=E2=80=99t install >>>>>>>>>>>> plugins that don=E2=80=99t match the current Erlang=20 >>>>>>>>>>>> version, gives semi-sensible error messages=20 >>>>>>>>>>>> (wrapped in a HTTP 500 response :) 6. Require >>>>>>>>>>>> a pretty strict format for binary releases. >>>>>>>>>>>>=20 >>>>>>>>>>>>=20 >>>>>>>>>>>> ## Roadmap >>>>>>>>>>>>=20 >>>>>>>>>>>> Here=E2=80=99s a list of things this first iterations >>>>>>>>>>>> does and doesn=E2=80=99t do: >>>>>>>>>>>>=20 >>>>>>>>>>>> - Pure Erlang plugins only. No C-dependencies, >>>>>>>>>>>> no JavaScript, no >>>>>>>>>> nothing. >>>>>>>>>>>> - No C-dependencies. - Install a plugin via >>>>>>>>>>>> Futon (or HTTP call). Admin only. - A hardcoded >>>>>>>>>>>> list of plugins in Futon. - Loads a >>>>>>>>>>>> pre-packaged, pre-compiled .tar.gz file from a >>>>>>>>>>>> URL. - Only installs if Erlang version matches. >>>>>>>>>>>> - No security checking of binaries. - No >>>>>>>>>>>> identity checking of binaries. >>>>>>>>>>>>=20 >>>>>>>>>>>> Here are a few things I want to add before I >>>>>>>>>>>> call it MVP*: >>>>>>>>>>>>=20 >>>>>>>>>>>> - Uninstall a plugin via Futon (or HTTP call).=20 >>>>>>>>>>>> Admin only. - Only installs if CouchDB version=20 >>>>>>>>>>>> matches. - Binaries must be published on=20 >>>>>>>>>>>> *.apache.org (http://apache.org). - Register=20 >>>>>>>>>>>> installed plugins in the config system. - Make >>>>>>>>>>>> sure plugins start with the next restart of >>>>>>>>>>>> CouchDB. - Show when a particular plugin is >>>>>>>>>>>> installed. >>>>>>>>>>>>=20 >>>>>>>>>>>> *MVP hopefully means you agree we can ship >>>>>>>>>>>> this with a few warnings so people can get a >>>>>>>>>>>> hang of it. >>>>>>>>>>>>=20 >>>>>>>>>>>> Here is a rough list of features squared >>>>>>>>>>>> against future milestones: >>>>>>>>>>>>=20 >>>>>>>>>>>> Milestone 2: Be creator friendly - Make it easy >>>>>>>>>>>> to build a CouchDB plugin by providing one or >>>>>>>>>>>> more easy to start templates. - Make it easy to >>>>>>>>>>>> publish new plugins and new versions of >>>>>>>>>>>> existing >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> plugins. >>>>>>>>>>>> - Make it easy to supply packages for multiple=20 >>>>>>>>>>>> Erlang & CouchDB >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> versions. >>>>>>>>>>>>=20 >>>>>>>>>>>> Milestone 3: Public registry - Instead of=20 >>>>>>>>>>>> hardcoding a list of plugins into >>>>>>>>>>>> Futon/Fauxton, we load a list of applicable >>>>>>>>>>>> plugins from a central (and configurable) >>>>>>>>>>>> plugins repository. - This allows plugin >>>>>>>>>>>> authors to publish new plugins and new versions >>>>>>>>>>>> of existing plugins independently. >>>>>>>>>>>>=20 >>>>>>>>>>>> Milestone 4: Other Languages - Figure out how >>>>>>>>>>>> to handle C-dependencies for Erlang plugins. - >>>>>>>>>>>> Figure out how to allow other language plugins >>>>>>>>>>>> (c.f. non-JS query servers) >>>>>>>>>>>>=20 >>>>>>>>>>>> Milestone X: Later - Add some=20 >>>>>>>>>>>> account/identity/maybe crypto-web-of-trust >>>>>>>>>>>> system for authors to publish =E2=80=9Clegit=E2=80=9D plugins. >>>>>>>>>>>> - Sign & verify individual releases. >>>>>>>>>>>>=20 >>>>>>>>>>>> A few more things that can happen concurrently=20 >>>>>>>>>>>> depending on what plugins require: - Integrate=20 >>>>>>>>>>>> Erlang/JS tests in the installation - >>>>>>>>>>>> Integrate docs >>>>>>>>>>>>=20 >>>>>>>>>>>>=20 >>>>>>>>>>>> ## How it works >>>>>>>>>>>>=20 >>>>>>>>>>>> This plugin system lives in `src/couch_plugins` >>>>>>>>>>>> and is a tiny CouchDB module. >>>>>>>>>>>>=20 >>>>>>>>>>>> It exposes one new API endpoint `/_plugins` >>>>>>>>>>>> that an admin user can POST to. >>>>>>>>>>>>=20 >>>>>>>>>>>> The additional Futon page lives at=20 >>>>>>>>>>>> /_utils/plugins.html it is hardcoded. >>>>>>>>>>>>=20 >>>>>>>>>>>> Futon (or you) post an object to `/_plugins` >>>>>>>>>>>> with four properties: >>>>>>>>>>>>=20 >>>>>>>>>>>> { "name": "geocouch", // name of the plugin, >>>>>>>>>>>> must be unique "url": >>>>>>>>>>>> "http://people.apache.org/~jan", // =E2=80=9Cbase URL=E2=80=9D >>>>>>>>>>>> for plugin >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> releases (see below) >>>>>>>>>>>> "version": "couchdb1.2.x_v0.3.0-11-gd83ba22", >>>>>>>>>>>> // whatever version >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> internal to the plugin >>>>>>>>>>>> "checksums": { "R15B03":=20 >>>>>>>>>>>> "ZetgdHj2bY2w37buulWVf3USOZs=3D" // base64=E2=80=99d sha=20 >>>>>>>>>>>> hash >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> over the binary >>>>>>>>>>>> } } >>>>>>>>>>>>=20 >>>>>>>>>>>> `couch_plugins` then attempts to download a >>>>>>>>>>>> .tar.gz from this location: >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> http://people.apache.org/~jan/geocouch-couchdb1.2.x_v0.3.0-12-g4e= a0bea-R15B03.tar.gz >>>=20 > It should be obvious how the URL is constructed from the POST data. >>>>>>>>>>>> (This url is live, feel free to play around >>>>>>>>>>>> with this tarball). >>>>>>>>>>>>=20 >>>>>>>>>>>> Next it calculates the sha hash for the >>>>>>>>>>>> downloaded .tar.gz file and matches it against >>>>>>>>>>>> the correct version in the `checksums` >>>>>>>>>>>> parameter. >>>>>>>>>>>>=20 >>>>>>>>>>>> If that succeeds, we unpack the .tar.gz file=20 >>>>>>>>>>>> (currently in `/tmp`, need to find a better >>>>>>>>>>>> place for this) and adds the extracted >>>>>>>>>>>> directory to the Erlang code path >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> (`code:add_path("/tmp/couchdb_plugins/geocouch-couchdb1.2.x_v0.3.= 0-12-g4ea0bea-R15B03/ebin")`) >>>=20 > and loads the included application (`application:load(geocouch)`). >>>>>>>>>>>>=20 >>>>>>>>>>>> Then it looks into the `./config` directory >>>>>>>>>>>> that lives next to `ebin/` in the plugin >>>>>>>>>>>> directory for a file `config.erlt` >>>>>>>>>>>> (=E2=80=9Cerl-terms=E2=80=9D). with a list of configuration >>>>>>>>>>>> parameters to load. We parse the file and set >>>>>>>>>>>> the config directives one by one. >>>>>>>>>>>>=20 >>>>>>>>>>>> If that all goes to plan, we report success >>>>>>>>>>>> back to the HTTP caller. >>>>>>>>>>>>=20 >>>>>>>>>>>> That=E2=80=99s it! :) >>>>>>>>>>>>=20 >>>>>>>>>>>> It=E2=80=99s deceptively simple, probably does a few >>>>>>>>>>>> things very wrong and leaves a few things open >>>>>>>>>>>> (see above). >>>>>>>>>>>>=20 >>>>>>>>>>>> One open question I=E2=80=99d like an answer for is >>>>>>>>>>>> finding a good location to unpack & install the >>>>>>>>>>>> plugin files that isn=E2=80=99t `tmp`. If the answer is >>>>>>>>>>>> different for a pre-BigCouch/rcouch-merge and=20 >>>>>>>>>>>> post-BigCouch/rcouch- merge world, I=E2=80=99d love to >>>>>>>>>>>> know :) >>>>>>>>>>>>=20 >>>>>>>>>>>>=20 >>>>>>>>>>>> ## Code >>>>>>>>>>>>=20 >>>>>>>>>>>> The main branch for this is >>>>>>>>>>>> 1867-feature-plugins: >>>>>>>>>>>>=20 >>>>>>>>>>>> ASF: >>>>>>>>>> https://git-wip-us.apache.org/repos/asf?p=3Dcouchdb.git;a=3Dlog;h= =3Drefs/heads/1867-feature-plugins >>>=20 > GitHub: https://github.com/janl/couchdb/compare/1867-feature-plugins >>>>>>>>>>>>=20 >>>>>>>>>>>> I created a branch on GeoCouch that adds a few=20 >>>>>>>>>>>> lines to its `Makefile` that shows how a >>>>>>>>>>>> binary package is built: >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> https://github.com/janl/geocouch/compare/couchbase:couchdb1.3.x..= .couchdb1.3.x-plugins >>>=20 > * * * >>>>>>>>>>>>=20 >>>>>>>>>>>> I hope you like this :) Please comment and >>>>>>>>>>>> improve heavily! >>>>>>>>>>>>=20 >>>>>>>>>>>> Let me know if you have any questions :) >>>>>>>>>>>>=20 >>>>>>>>>>>> If you have any criticism, please phrase it in >>>>>>>>>>>> a way that we can use to improve this, thanks! >>>>>>>>>>>>=20 >>>>>>>>>>>> Best, Jan -- >=20 > -----BEGIN PGP SIGNATURE----- > Version: GnuPG v1.4.12 (GNU/Linux) > Comment: Using GnuPG with undefined - http://www.enigmail.net/ >=20 > iQEcBAEBAgAGBQJR/ntiAAoJEAq942X94y8mpkYIAK1w9MBOmXSw3BZc9OJf55W1 > 4KRHvtvpyUy5BTZyoCHCeKquHDIy+rRJTnX69y3k4yXwEAG6kocJ5UldWUSn/tX8 > RAnEyIeTQs5YAjybutPIjpw6ClXgJnxON4sDCjvDPMoj3csE2RbxHqMD1udSGDd5 > OuUdqYBvC2g8XpNq3hbxtX7O0982s4OOfdJAywTpQ4vrcBhql+5m70YUiM+zpRF2 > JjQxngk9110aMEBDm6CgjW6z7XtwMbWqsXuFDfr5/RvFwBdAD+PksfWvPyUOmq2x > Bga7Dqa+clBN6AmA6oNGibXcIAjL//vqynFK4XKOSBchKuLurPypE5b30pkzmX4=3D > =3DS/C3 > -----END PGP SIGNATURE-----