├── README.md ├── bin ├── USAGE └── cmd.js ├── index.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # ssb-webify 2 | 3 | > convert a local file hierarchy into a scuttlebutt website 4 | 5 | ## CLI Usage 6 | 7 | ### Static website (one-time publish) 8 | 9 | ``` 10 | $ npm install --global ssb-webify 11 | 12 | $ mkdir my_site 13 | $ echo '
version two
' > my_site/index.html 34 | 35 | $ ssb-webify publish my_site 36 | Published dynamic website: %aH7TzpolO4FmTQxOVyCeHI2QJavv6amL2xgLBIEk8Ro=.sha256 (%25aH7TzpolO4FmTQxOVyCeHI2QJavv6amL2xgLBIEk8Ro%3D.sha256) 37 | ``` 38 | 39 | You can plug the resulting web hash (the one in parentheses) into any ssb 40 | software that supports websites (currently only 41 | [patchfoo](https://github.com/ssbc/patchfoo) and public 42 | [ssb-viewers](https://github.com/ssbc/ssb-viewer)s) and view it. 43 | 44 | The [resolver](https://github.com/noffle/ssb-web-resolver) takes care of 45 | recursively looking up blobs to find the webpage or file you want to view and 46 | calls it up from your local `sbot`. 47 | 48 | ## API 49 | 50 | You can also use this module as an API. 51 | 52 | ```js 53 | var webify = require('ssb-webify') 54 | ``` 55 | 56 | ### webify.publish(filename, cb) 57 | 58 | Recursively *webify and publish* `filename`, which is a file or directory. `cb` 59 | is called with the signature `cb(err, id)`, where `id` is the ssb blob id of the 60 | website root. 61 | 62 | Right now this only publishes the blobs (ie. a static website). 63 | 64 | ### webify.init(cb) 65 | 66 | Publishes a `web-init` message to sbot and returns the key. 67 | 68 | The published message's `content` just looks like 69 | 70 | ```json 71 | { 72 | "type": "web-init" 73 | } 74 | ``` 75 | 76 | but its `key` is now the identifier for the mutable website. 77 | 78 | ### webify.update(siteKey, hash, cb) 79 | 80 | Publishes a message updating the website key `siteKey` to the website at `hash`. 81 | 82 | For example, creating a dynamic website and updating it might look like this: 83 | 84 | ```js 85 | webify.init(function (_, key) { 86 | webify.publish('my_site/', function (_, hash) { 87 | webify.update(key, hash, function () { 88 | console.log('done') 89 | }) 90 | }) 91 | }) 92 | ``` 93 | 94 | The published message's `content` looks like 95 | 96 | ```json 97 | { 98 | "type": "web-root", 99 | "root": "&9DBDncPbI3cvrGFhpZuF5xNIDlZTRsFLg50CybNuZQs=.sha256", 100 | "site": "%RzC0zlEBmeGbQNmVvzKXEED9h+nNRTBLgyS3lWg9gSA=.sha256" 101 | } 102 | ``` 103 | 104 | where `root` is the new blob to point the website at, and `site` is the identifier (key) from `webify.init()`. 105 | 106 | ## License 107 | 108 | ISC 109 | -------------------------------------------------------------------------------- /bin/USAGE: -------------------------------------------------------------------------------- 1 | USAGE: ssb-webify 2 | 3 | init 4 | Start a new dynamic website. Publishes a new `web-init` message to SSB and 5 | records it in `.ssb-web`. 6 | 7 | publish DIR 8 | Publish DIR as a website to SSB. If there is a local `.ssb-web` file in the 9 | current directory, a `web-root` message is also published pointing to this 10 | as the latest version of the site. Otherwise, it is published as a static 11 | site (just the blobs are published to SSB). 12 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path') 4 | var fs = require('fs') 5 | var webify = require('..') 6 | var argv = require('minimist')(process.argv) 7 | var mkdirp = require('mkdirp') 8 | var spawn = require('child_process').spawn 9 | 10 | if (argv.version || argv.v) { 11 | console.log(require(path.join(__dirname, '..', 'package.json')).version) 12 | return 13 | } 14 | 15 | var cmd = argv._[2] 16 | if (!cmd) return printUsageAndDie() 17 | 18 | if (cmd === 'publish') { 19 | if (!argv._[3]) return printUsageAndDie() 20 | var filename = argv._[3] 21 | webify.publish(filename, function (err, hash) { 22 | if (err) throw err 23 | var keydir = path.join('.ssb-web', 'key') 24 | if (fs.existsSync(keydir)) { 25 | var key = fs.readFileSync(path.join('.ssb-web', 'key'), 'utf-8') 26 | fs.writeFileSync(path.join('.ssb-web', 'root'), hash, 'utf-8') 27 | var args = ['publish', '--type', 'web-root', '--root', hash, '--site', key] 28 | spawn('sbot', args) 29 | .once('exit', function (code) { 30 | if (!code) console.log('Published dynamic ssb-web site:', key, '('+encodeURIComponent(key)+')') 31 | else console.log('error', code) 32 | }) 33 | } else { 34 | console.log('Published static ssb-web site:', hash, '('+encodeURIComponent(hash)+')') 35 | } 36 | }) 37 | } 38 | 39 | if (cmd === 'init') { 40 | if (fs.existsSync(path.join('.ssb-web', 'key'))) { 41 | console.log('ERROR: A .ssb-web directory already exists here.') 42 | process.exit(1) 43 | } 44 | webify.init(function (err, key) { 45 | mkdirp.sync('.ssb-web') 46 | fs.writeFileSync(path.join('.ssb-web', 'key'), key, 'utf-8') 47 | console.log('Initialized new ssb-web site:', key, '('+encodeURIComponent(key)+')') 48 | }) 49 | } 50 | 51 | function printUsageAndDie () { 52 | fs.createReadStream(path.join(__dirname, 'USAGE')).pipe(process.stdout) 53 | .once('end', function () { 54 | process.exit(0) 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var fs = require('fs') 3 | var spawn = require('child_process').spawn 4 | var collect = require('collect-stream') 5 | var once = require('once') 6 | 7 | module.exports = { 8 | init: init, 9 | publish: publish, 10 | update: update 11 | } 12 | 13 | function init (cb) { 14 | cb = once(cb) 15 | 16 | var p = spawn('sbot', 'publish --type web-init'.split(' ')) 17 | 18 | collect(p.stdout, function (err, res) { 19 | if (err) return cb(err) 20 | try { 21 | var json = JSON.parse(res) 22 | return cb(null, json.key) 23 | } catch (e) { 24 | return cb(e) 25 | } 26 | }) 27 | 28 | p.once('exit', function (code) { 29 | if (code) cb(new Error('sbot error code ' + code)) 30 | }) 31 | } 32 | 33 | function publish (filename, cb) { 34 | if (fs.statSync(filename).isDirectory()) { 35 | wrapDirRec(filename, done) 36 | } else { 37 | wrapFile(filename, done) 38 | } 39 | 40 | function done (err, hash) { 41 | if (err) return cb(err) 42 | cb(null, hash) 43 | } 44 | } 45 | 46 | function update (site, hash, cb) { 47 | var args = ['publish', '--type', 'web-root', '--root', hash, '--site', site] 48 | spawn('sbot', args) 49 | .once('exit', cb) 50 | } 51 | 52 | function wrapData (data, cb) { 53 | var p = spawn('sbot', ['blobs.add']) 54 | p.stdin.end(data) 55 | collect(p.stdout, function (err, res) { 56 | cb(err, res ? res.toString().trim() : undefined) 57 | }) 58 | } 59 | 60 | function wrapFile (filename, cb) { 61 | var data = fs.readFileSync(filename) 62 | wrapData(data, cb) 63 | } 64 | 65 | function wrapDirRec (dirname, cb) { 66 | fs.readdir(dirname, function (err, names) { 67 | if (err) return cb(err) 68 | 69 | var fullnames = names 70 | .filter(function (name) { 71 | return !name.startsWith('.') 72 | }) 73 | .map(function (name) { 74 | return { 75 | full: path.join(dirname, name), 76 | local: name 77 | } 78 | }) 79 | 80 | var res = { 81 | links: {} 82 | } 83 | 84 | var pending = fullnames.length + 1 85 | 86 | fullnames.forEach(function (name) { 87 | var fullname = name.full 88 | var shortname = name.local 89 | var stat = fs.statSync(fullname) 90 | if (stat.isDirectory()) { 91 | wrapDirRec(fullname, function (err, hash) { 92 | if (err) return done(err) 93 | res.links[shortname] = hash 94 | done() 95 | }) 96 | } else if (stat.isFile()) { 97 | wrapFile(fullname, function (err, hash) { 98 | if (err) return done(err) 99 | res.links[shortname] = hash 100 | done() 101 | }) 102 | } else { 103 | console.log('skipping', shortname) 104 | done() 105 | } 106 | }) 107 | 108 | done() 109 | 110 | var error 111 | function done (err) { 112 | if (err) error = err 113 | if (--pending) return 114 | if (error) return cb(error) 115 | 116 | wrapData(JSON.stringify(res), cb) 117 | } 118 | }) 119 | } 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssb-webify", 3 | "description": "convert a static website into an ssb website", 4 | "author": "Stephen Whitmore