├── .gitignore ├── README.md ├── assets ├── Romaine_128.png └── Romaine_16.png ├── background.js ├── bower.json ├── common.js ├── config.js ├── create-chroot.css ├── create-chroot.html ├── create-chroot.js ├── elements ├── chroot-info.html ├── chroot-info.js ├── dynamic-list.html ├── dynamic-list.js ├── multi-list.html └── multi-list.js ├── index.html ├── index.js ├── manifest.json ├── meteor.js └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Romaine 2 | Crouton/chroot GUI for Chrome OS as a packaged app. Requires developer mode. 3 | 4 | Not in any way ready for anything yet. 5 | 6 | For more infomation, check out [romaine-head](https://github.com/danopia/romaine-head). 7 | 8 | ![Select targets screen](http://i.imgur.com/a2cmqnF.png) 9 | ![Building chroot screen](http://i.imgur.com/kMAsxJs.png) 10 | -------------------------------------------------------------------------------- /assets/Romaine_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danopia/Romaine/fa5af45021a676f2269072d57e1289f6c7fb9ec7/assets/Romaine_128.png -------------------------------------------------------------------------------- /assets/Romaine_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danopia/Romaine/fa5af45021a676f2269072d57e1289f6c7fb9ec7/assets/Romaine_16.png -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Listens for the app launching then creates the window. 3 | * 4 | * @see http://developer.chrome.com/apps/app.runtime.html 5 | * @see http://developer.chrome.com/apps/app.window.html 6 | */ 7 | chrome.app.runtime.onLaunched.addListener(function (launchData) { 8 | chrome.app.window.create( 9 | 'index.html', 10 | { 11 | id: 'mainWindow', 12 | frame: 'none', 13 | bounds: {width: 850, height: 500} 14 | } 15 | ); 16 | }); 17 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Romaine", 3 | "description": "", 4 | "homepage": "", 5 | "keywords": [], 6 | "author": "", 7 | "private": true, 8 | "dependencies": { 9 | "polymer": "Polymer/polymer#^1.0.0", 10 | "paper-elements": "PolymerElements/paper-elements#^1.0.0", 11 | "neon-animation": "PolymerElements/neon-animation#^1.0.6" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | window.Chroots = new Mongo.Collection('chroots'); 2 | 3 | function capitalizeFirstLetter (str) { 4 | return str.charAt(0).toUpperCase() + str.slice(1); 5 | } 6 | 7 | function listDistros (callback) { 8 | var distroRegex = /Recognized ([^ ]+) releases:((?:\n (?:[\w*]+ ?)+)+)/g; 9 | var releaseRegex = /\w+\*?/g; 10 | 11 | var remarks = { 12 | ubuntu: ' (recommended)', 13 | }; 14 | var descriptions = { 15 | debian: 'In case you dislike Ubuntu', 16 | kali: 'Information Security / pen testing distro', 17 | ubuntu: 'General-purpose distro with the widest package coverage', 18 | }; 19 | 20 | chrome.runtime.sendMessage({cmd: 'run crouton', args: ['-r', 'list']}, function (response) { 21 | callback(response.output.match(distroRegex).map(function (raw) { 22 | var lines = raw.split('\n'); 23 | 24 | var distro = lines.shift().split(' ')[1]; 25 | var releases = lines.join().match(releaseRegex).map(function (token) { 26 | var release = { 27 | key: token.replace('*', ''), 28 | distro: distro, 29 | supported: token[token.length - 1] != '*', 30 | }; 31 | 32 | release.label = capitalizeFirstLetter(release.key) + (release.supported ? ' (well-supported)' : ''); 33 | return release; 34 | }).reverse(); 35 | 36 | return { 37 | key: distro, 38 | label: capitalizeFirstLetter(distro) + (remarks[distro] || ''), 39 | description: descriptions[distro] || '', 40 | releases: releases, 41 | }; 42 | })); 43 | }); 44 | } 45 | 46 | function listTargets (callback) { 47 | var regex = /^[\w\-]+(\n\W+.+)+/mg; 48 | 49 | chrome.runtime.sendMessage({cmd: 'run crouton', args: ['-t', 'list']}, function (response) { 50 | callback(response.output.match(regex).map(function (raw) { 51 | var lines = raw.split('\n\t'); 52 | 53 | return { 54 | key: lines[0], 55 | label: capitalizeFirstLetter(lines[0].replace('-', ' ')), 56 | description: lines[1], 57 | requirements: lines[2] && lines[2].split(' ').slice(1) 58 | }; 59 | })); 60 | }); 61 | } -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | __meteor_runtime_config__ = {}; 2 | __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = 'http://localhost:6206/app'; 3 | -------------------------------------------------------------------------------- /create-chroot.css: -------------------------------------------------------------------------------- 1 | #build-chroot { 2 | background-color: #cec; 3 | color: #363; 4 | font-size: 200%; 5 | margin: 2em 5em; 6 | } 7 | #build-chroot iron-icon { 8 | width: 32px; 9 | height: 32px; 10 | } 11 | 12 | pre.shell { 13 | background-color: #222; 14 | color: #ccc; 15 | -webkit-user-select: initial; 16 | } 17 | pre.shell:before { 18 | content: '$ '; 19 | color: #666; 20 | } 21 | 22 | h3 { 23 | margin: 1em 25px; 24 | } -------------------------------------------------------------------------------- /create-chroot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Build New Chroot 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Build New Chroot 37 | 38 | 39 | 40 | 41 | 42 | Distribution 43 | Release 44 | Targets 45 | Settings 46 | Review 47 | 48 | 49 |
50 | 52 | 53 |

Select a Linux distribution to use

54 | 55 |
56 | 57 | 58 |

Choose a distro version

59 | 60 |
61 | 62 | 63 |

Select components to install in the chroot

64 | 65 |
66 | 67 | 68 |

Extra settings for the chroot setup

69 | 70 | 71 | 72 |
73 |

Encrypt

74 |
Require a password to unlock the chroot whenever you want to use it.
75 |
76 |
77 | 78 | 79 | 80 |
81 |

Update

82 |
Update the existing chroot of this release, if any. Allows for adding targets.
83 |
84 |
85 |
86 | 87 | 88 |

Ready to go!

89 |
90 | 91 | build chroot   92 | 93 | 94 |
95 |
96 |
97 |
98 |
99 | 100 | 101 | 102 | 103 | 104 | next step   105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
sudo sh ~/Downloads/crouton 
113 |
114 |
115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /create-chroot.js: -------------------------------------------------------------------------------- 1 | // Rig close button 2 | document.querySelector('[icon=close]').onclick = function () { 3 | window.close(); 4 | }; 5 | 6 | // Set up tabs and navigation 7 | var steps = ['distro', 'release', 'targets', 'settings', 'review']; 8 | var wizard = document.querySelector('#wizard'); 9 | var tabs = document.querySelector('paper-tabs'); 10 | var pages = document.querySelector('neon-animated-pages'); 11 | tabs.addEventListener('iron-select', function () { 12 | pages.selected = tabs.selected; 13 | handleStepBar(tabs.selected); 14 | 15 | output.opened = false; 16 | }); 17 | 18 | document.querySelector('#next-step').addEventListener('click', function () { 19 | var step = tabs.selected + 1; 20 | tabs.selected = step; 21 | pages.selected = step; 22 | handleStepBar(step); 23 | }); 24 | 25 | // Load distro/release list 26 | var distros = document.querySelector('#distros'); 27 | var releases = document.querySelector('#releases'); 28 | var distroMap = {}; 29 | 30 | listDistros(function (obj) { 31 | distros.items = obj.reverse(); 32 | 33 | obj.forEach(function (that) { 34 | distroMap[that.key] = that; 35 | }); 36 | }); 37 | 38 | // Helper to switch tabs programmatically 39 | function switchTab (key) { 40 | setTimeout(function () { 41 | tabs.selected = key; 42 | pages.selected = key; 43 | handleStepBar(key); 44 | }, 250); 45 | } 46 | 47 | // Step bar (next step, etc) logic 48 | var stepBar = document.querySelector('#step-bar'); 49 | function handleStepBar (tab) { 50 | stepBar.opened = (tab == 2) || (tab == 3); // targets, settings 51 | buildCommand(); 52 | } 53 | 54 | // Handle selecting a distro 55 | document.querySelector('#distros').onselect = function () { 56 | switchTab(1); 57 | releases.items = (distroMap[distros.selected] || {}).releases; 58 | }; 59 | 60 | // Handle selecting a release 61 | document.querySelector('#releases').onselect = function () { 62 | switchTab(2); 63 | }; 64 | 65 | // Load target list 66 | var targets = document.querySelector('#targets'); 67 | listTargets(function (obj) { 68 | targets.items = obj; 69 | }); 70 | 71 | // Build the crouton command to run 72 | var shell = document.querySelector('#shell'); 73 | var cmdLine = document.querySelector('#cmd-line'); 74 | function buildCommand () { 75 | var args = []; 76 | 77 | if (releases.selected) { 78 | args.push('-r'); 79 | args.push(releases.selected); 80 | } 81 | 82 | if (targets.selectedValues.length) { 83 | args.push('-t'); 84 | args.push(targets.selectedValues.join(',')); 85 | } 86 | 87 | if (document.querySelector('#encrypt').checked) { 88 | args.push('-e'); 89 | } 90 | 91 | if (document.querySelector('#update').checked) { 92 | args.push('-u'); 93 | } 94 | 95 | cmdLine.innerText = args.join(' '); 96 | shell.opened = true; 97 | } 98 | 99 | // Lodge command builder hooks 100 | targets.onselect = buildCommand; 101 | var i, boxes = document.querySelectorAll('paper-checkbox'); 102 | for (i = 0; i < boxes.length; i++) { 103 | boxes[i].addEventListener('change', buildCommand); 104 | } 105 | 106 | // Run crouton 107 | var buildButton = document.querySelector('#build-chroot'); 108 | buildButton.addEventListener('click', function () { 109 | buildCommand(); 110 | 111 | // TODO: main window might not be open 112 | chrome.app.window.get('mainWindow').contentWindow.postMessage({ 113 | event: 'add chroot', 114 | command: cmdLine.innerText, 115 | chroot: { 116 | key: releases.selected, 117 | label: releases.selected, 118 | state: 'building', 119 | description: '(building...)', 120 | }, 121 | }, 'chrome-extension://illiapbpjpagcchpdmdaonjpfpphgjhb'); 122 | window.close(); 123 | }); 124 | -------------------------------------------------------------------------------- /elements/chroot-info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | 66 | 67 | -------------------------------------------------------------------------------- /elements/chroot-info.js: -------------------------------------------------------------------------------- 1 | Polymer({ 2 | is: 'chroot-info', 3 | properties: { chroot: { notify: true } }, 4 | ready: function () { 5 | this.chroot = { label: 'loading...' }; 6 | this.versions = {}; 7 | 8 | this.runCommand = function (args, cb) { 9 | var opts = { 10 | cmd: 'run in chroot', 11 | args: args, 12 | chroot: this.chroot.key 13 | }; 14 | chrome.runtime.sendMessage(opts, function (response) { 15 | cb.call(this, response.output); 16 | }.bind(this)); 17 | }; 18 | 19 | this.getVersions = function () { 20 | this.runCommand(['croutonversion'], function (output) { 21 | output.split('\n').forEach(function (line) { 22 | var pair = line.split(': '); 23 | this.set('versions.' + pair[0], pair[1]); 24 | }, this); 25 | }); 26 | }; 27 | 28 | this.checkForUpdates = function () { 29 | this.runCommand([ 30 | 'croutonversion', 31 | '-u' 32 | ], function (output) { 33 | var current = output.match(/crouton: version (.+)/)[1]; 34 | var latest = output.match(/latest: version (.+)/)[1]; 35 | 36 | if (current == latest) { 37 | this.$.update.$$('paper-material').innerText = 'Up to date'; 38 | this.$.update.disabled = true; 39 | } else { 40 | this.$.update.$$('paper-material').innerText = 'Update available'; 41 | this.$.update.style.backgroundColor = '#c5e1a5'; // light green l3 42 | } 43 | }); 44 | }; 45 | 46 | this.selectChroot = function (chroot) { 47 | this.chroot = chroot; 48 | this.getVersions(); 49 | }; 50 | 51 | this.execCommand = function (event) { 52 | event.preventDefault(); 53 | var cmd = this.$.command.value; 54 | var outputBox = this.$.output; 55 | 56 | this.runCommand(cmd.split(' '), function (output) { 57 | outputBox.innerText = output; 58 | }); 59 | }; 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /elements/dynamic-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /elements/dynamic-list.js: -------------------------------------------------------------------------------- 1 | Polymer({ 2 | is: 'dynamic-list', 3 | properties: { 4 | items: { 5 | type: Array, 6 | value: [{ description: 'loading...' }], 7 | }, 8 | selected: { 9 | value: null, 10 | notify: true, 11 | }, 12 | noIcons: { 13 | type: Boolean, 14 | value: false, 15 | }, 16 | }, 17 | 18 | computeIcon: function (selected, key) { 19 | return 'radio-button-' + ((selected == key) ? 'checked' : 'unchecked'); 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /elements/multi-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /elements/multi-list.js: -------------------------------------------------------------------------------- 1 | Polymer({ 2 | is: 'multi-list', 3 | properties: { 4 | items: { 5 | type: Array, 6 | value: [{ description: 'loading...' }], 7 | }, 8 | selectedValues: { 9 | value: [], 10 | notify: true, 11 | }, 12 | noIcons: { 13 | type: Boolean, 14 | value: false, 15 | }, 16 | }, 17 | 18 | computeIcon: function (selectedList, key) { 19 | return 'check-box' + ((selectedList.base.indexOf(key) >= 0) ? '' : '-outline-blank'); 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Romaine 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Romaine 32 | 33 | 34 | 35 | 36 |
37 | 38 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('chroot list'); 2 | 3 | var closeButton = document.querySelector('[icon=close]'); 4 | var newButton = document.querySelector('paper-fab'); 5 | var chrootList = document.querySelector('dynamic-list'); 6 | var chrootInfo = document.querySelector('chroot-info'); 7 | 8 | closeButton.onclick = function () { window.close(); }; 9 | 10 | 11 | //////////////////// 12 | // Chroot Creation 13 | 14 | newButton.onclick = function () { 15 | chrome.app.window.create('create-chroot.html', { 16 | id: 'createChroot', 17 | frame: 'none', 18 | bounds: {width: 700, height: 600} 19 | }); 20 | }; 21 | 22 | /* 23 | chrome.runtime.sendMessage({ 24 | cmd: 'build chroot', 25 | args: evt.data.command.split(' '), 26 | extras: { 27 | stdin: 'user\n', 28 | }, 29 | */ 30 | 31 | //////////////////// 32 | 33 | var chroots = {}; 34 | var chrootArray = []; 35 | 36 | // get a chroot list at startup 37 | Meteor.autorun(function () { 38 | chrootArray = Chroots.find().map(function (chroot) { 39 | return (chroots[chroot._id] = { 40 | key: chroot._id, 41 | label: chroot._id, 42 | state: chroot.status, 43 | description: '(' + chroot.status + ')', 44 | // also has distro 45 | }); 46 | }); 47 | 48 | chrootList.notifyPath('items', chrootArray); 49 | }); 50 | 51 | // var descPath = 'items.' + chrootArray.indexOf(chroot) + '.description'; 52 | // chrootList.notifyPath(descPath, chroot.description); 53 | 54 | document.querySelector('dynamic-list').onselect = function (e, item) { 55 | var chroot = chroots[this.selected]; 56 | 57 | // Start chroot if it isn't running 58 | console.log('i am', chroot); 59 | if (chroot.state == 'stopped') { 60 | Meteor.call('start chroot', chroot.key); 61 | } 62 | 63 | chrootInfo.selectChroot(chroot); 64 | }; 65 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Romaine", 4 | "short_name": "Romaine", 5 | "description": "", 6 | "version": "0.0.5", 7 | "minimum_chrome_version": "38", 8 | 9 | "icons": { 10 | "16": "assets/Romaine_16.png", 11 | "128": "assets/Romaine_128.png" 12 | }, 13 | 14 | "app": { 15 | "background": { 16 | "scripts": ["background.js"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | font-family: RobotoDraft, sans-serif; 7 | color: #333; 8 | margin: 0; 9 | -webkit-user-select: none; 10 | -moz-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | -webkit-tap-highlight-color: rgba(0,0,0,0); 14 | -webkit-touch-callout: none; 15 | } 16 | 17 | section h3 { 18 | margin: 20px 30px 10px; 19 | } 20 | 21 | a, a:hover { 22 | text-decoration: none; 23 | } 24 | 25 | paper-toolbar.draggable { 26 | -webkit-app-region: drag; 27 | background-color: #689f38; /* light-green darken-2 */ 28 | box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.2); 29 | color: #fff; 30 | font-size: 20px; 31 | font-weight: 400; 32 | } 33 | 34 | paper-toolbar.draggable span { 35 | color: #dcedc8; /* light-green lighten-4 */ 36 | } 37 | 38 | paper-toolbar paper-icon-button, core-toolbar paper-tabs { 39 | -webkit-app-region: no-drag; 40 | } 41 | 42 | paper-checkbox { 43 | padding: 2em; 44 | padding-left: 4em; 45 | } 46 | 47 | paper-fab { 48 | position: fixed !important; 49 | bottom: 1em; 50 | right: 1em; 51 | z-index: 1; 52 | } 53 | 54 | paper-tabs { 55 | background-color: #689f38; /* light-green darken-2 */ 56 | color: #fff; 57 | } 58 | 59 | paper-tabs[noink][nobar] paper-tab.core-selected { 60 | color: #ffff8d; 61 | } 62 | 63 | [flex] { 64 | flex: 1; 65 | } 66 | 67 | iron-selector .iron-selected { 68 | background: rgba(0, 0, 0, 0.1); 69 | } 70 | 71 | .sidebar { 72 | background-color: #9ccc65; /* light-green lighten-1 */ 73 | } 74 | 75 | .split > * { 76 | overflow: auto; 77 | } 78 | 79 | body { 80 | display: flex; 81 | flex-direction: column; 82 | background: #cfd8dc; 83 | } 84 | 85 | .horizontal.split { 86 | flex: 1; 87 | } 88 | 89 | .paper-card.title-text { 90 | padding-bottom: 0 !important; 91 | } 92 | 93 | .paper-card .card-content { 94 | padding-top: 0 !important; 95 | } 96 | 97 | --------------------------------------------------------------------------------