├── .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 | 
9 | 
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 |
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 |
5 |
6 |
7 |
8 | - release
- {{versions.release}}
9 | - xmethod
- {{versions.xmethod}}
10 | - targets
- {{versions.targets}}
11 |
12 |
13 |
14 |
15 | Check for updates
16 |
17 |
18 |
19 |
20 |
21 |
31 |
32 |
33 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{item.label}}
14 |
15 |
16 | {{item.description}}
17 |
18 |
19 |
20 |
21 |
22 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{item.label}}
14 |
15 |
16 | {{item.description}}
17 |
18 |
19 |
20 |
21 |
22 |
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 |
--------------------------------------------------------------------------------