diff --git a/coder-base/apps/eyeball/app.js b/coder-apps/common/eyeball/app/app.js
similarity index 100%
rename from coder-base/apps/eyeball/app.js
rename to coder-apps/common/eyeball/app/app.js
diff --git a/coder-base/apps/eyeball/meta.json b/coder-apps/common/eyeball/app/meta.json
similarity index 81%
rename from coder-base/apps/eyeball/meta.json
rename to coder-apps/common/eyeball/app/meta.json
index 2013dda0..079a953d 100644
--- a/coder-base/apps/eyeball/meta.json
+++ b/coder-apps/common/eyeball/app/meta.json
@@ -1,6 +1,6 @@
{
"created": "2013-03-15",
- "modified": "2013-07-08",
+ "modified": "2014-01-14",
"color": "#f39c12",
"author": "Justin Windle",
"name": "Eyeball",
diff --git a/coder-base/static/apps/eyeball/css/index.css b/coder-apps/common/eyeball/static/css/index.css
similarity index 97%
rename from coder-base/static/apps/eyeball/css/index.css
rename to coder-apps/common/eyeball/static/css/index.css
index b429b134..9f3a2a5d 100644
--- a/coder-base/static/apps/eyeball/css/index.css
+++ b/coder-apps/common/eyeball/static/css/index.css
@@ -4,7 +4,7 @@
}
html, body {
- background: #f2f25a;
+ background: #f9de2a;
text-align: center;
}
diff --git a/coder-base/static/apps/eyeball/js/index.js b/coder-apps/common/eyeball/static/js/index.js
similarity index 100%
rename from coder-base/static/apps/eyeball/js/index.js
rename to coder-apps/common/eyeball/static/js/index.js
diff --git a/coder-apps/common/eyeball/static/media/.gitignore b/coder-apps/common/eyeball/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/views/apps/eyeball/index.html b/coder-apps/common/eyeball/views/index.html
similarity index 100%
rename from coder-base/views/apps/eyeball/index.html
rename to coder-apps/common/eyeball/views/index.html
diff --git a/coder-base/apps/game2d/app.js b/coder-apps/common/game2d/app/app.js
similarity index 100%
rename from coder-base/apps/game2d/app.js
rename to coder-apps/common/game2d/app/app.js
diff --git a/coder-base/apps/game2d/meta.json b/coder-apps/common/game2d/app/meta.json
similarity index 100%
rename from coder-base/apps/game2d/meta.json
rename to coder-apps/common/game2d/app/meta.json
diff --git a/coder-base/static/apps/game2d/css/index.css b/coder-apps/common/game2d/static/css/index.css
similarity index 100%
rename from coder-base/static/apps/game2d/css/index.css
rename to coder-apps/common/game2d/static/css/index.css
diff --git a/coder-base/static/apps/game2d/js/index.js b/coder-apps/common/game2d/static/js/index.js
similarity index 100%
rename from coder-base/static/apps/game2d/js/index.js
rename to coder-apps/common/game2d/static/js/index.js
diff --git a/coder-apps/common/game2d/static/media/.gitignore b/coder-apps/common/game2d/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/views/apps/game2d/index.html b/coder-apps/common/game2d/views/index.html
similarity index 100%
rename from coder-base/views/apps/game2d/index.html
rename to coder-apps/common/game2d/views/index.html
diff --git a/coder-base/apps/hello_coder/app.js b/coder-apps/common/hello_coder/app/app.js
similarity index 100%
rename from coder-base/apps/hello_coder/app.js
rename to coder-apps/common/hello_coder/app/app.js
diff --git a/coder-base/apps/hello_coder/meta.json b/coder-apps/common/hello_coder/app/meta.json
similarity index 80%
rename from coder-base/apps/hello_coder/meta.json
rename to coder-apps/common/hello_coder/app/meta.json
index a849d826..418ab2d5 100644
--- a/coder-base/apps/hello_coder/meta.json
+++ b/coder-apps/common/hello_coder/app/meta.json
@@ -1,8 +1,8 @@
{
"created": "2013-05-08",
- "modified": "2013-07-17",
+ "modified": "2014-01-14",
"color": "#d977d4",
"author": "Jason Striegel",
"name": "Hello Coder",
"hidden": false
-}
+}
\ No newline at end of file
diff --git a/coder-base/static/apps/hello_coder/css/index.css b/coder-apps/common/hello_coder/static/css/index.css
similarity index 100%
rename from coder-base/static/apps/hello_coder/css/index.css
rename to coder-apps/common/hello_coder/static/css/index.css
diff --git a/coder-base/static/apps/hello_coder/js/index.js b/coder-apps/common/hello_coder/static/js/index.js
similarity index 97%
rename from coder-base/static/apps/hello_coder/js/index.js
rename to coder-apps/common/hello_coder/static/js/index.js
index 88414dd6..c188f333 100644
--- a/coder-base/static/apps/hello_coder/js/index.js
+++ b/coder-apps/common/hello_coder/static/js/index.js
@@ -49,6 +49,11 @@ var initialize = function() {
$canvas.attr('width', w);
$canvas.attr('height', h);
+ // Make sure numLines isn't a crazy number
+ if ( numLines > 10000 ) {
+ numLines = 10000;
+ }
+
for ( var x=0; x < numLines; x++ ) {
// top x position
diff --git a/coder-apps/common/hello_coder/static/media/.gitignore b/coder-apps/common/hello_coder/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/views/apps/hello_coder/index.html b/coder-apps/common/hello_coder/views/index.html
similarity index 97%
rename from coder-base/views/apps/hello_coder/index.html
rename to coder-apps/common/hello_coder/views/index.html
index 26984c73..573af7d5 100644
--- a/coder-base/views/apps/hello_coder/index.html
+++ b/coder-apps/common/hello_coder/views/index.html
@@ -27,7 +27,7 @@
Hello Coder
-
Get started with Coder by exploring this app.
+
Get started with Coder by exploring this app.
Click the edit button "</>" to dive in.
It's at the top right.
diff --git a/coder-base/apps/space_rocks_/app.js b/coder-apps/common/space_rocks_/app/app.js
similarity index 100%
rename from coder-base/apps/space_rocks_/app.js
rename to coder-apps/common/space_rocks_/app/app.js
diff --git a/coder-base/apps/space_rocks_/meta.json b/coder-apps/common/space_rocks_/app/meta.json
similarity index 100%
rename from coder-base/apps/space_rocks_/meta.json
rename to coder-apps/common/space_rocks_/app/meta.json
diff --git a/coder-base/static/apps/space_rocks_/css/index.css b/coder-apps/common/space_rocks_/static/css/index.css
similarity index 100%
rename from coder-base/static/apps/space_rocks_/css/index.css
rename to coder-apps/common/space_rocks_/static/css/index.css
diff --git a/coder-base/static/apps/space_rocks_/js/index.js b/coder-apps/common/space_rocks_/static/js/index.js
similarity index 100%
rename from coder-base/static/apps/space_rocks_/js/index.js
rename to coder-apps/common/space_rocks_/static/js/index.js
diff --git a/coder-apps/common/space_rocks_/static/media/.gitignore b/coder-apps/common/space_rocks_/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/static/apps/space_rocks_/media/die_ship.mp3 b/coder-apps/common/space_rocks_/static/media/die_ship.mp3
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/die_ship.mp3
rename to coder-apps/common/space_rocks_/static/media/die_ship.mp3
diff --git a/coder-base/static/apps/space_rocks_/media/die_ship.ogg b/coder-apps/common/space_rocks_/static/media/die_ship.ogg
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/die_ship.ogg
rename to coder-apps/common/space_rocks_/static/media/die_ship.ogg
diff --git a/coder-base/static/apps/space_rocks_/media/die_ship.wav b/coder-apps/common/space_rocks_/static/media/die_ship.wav
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/die_ship.wav
rename to coder-apps/common/space_rocks_/static/media/die_ship.wav
diff --git a/coder-base/static/apps/space_rocks_/media/die_spacerock.mp3 b/coder-apps/common/space_rocks_/static/media/die_spacerock.mp3
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/die_spacerock.mp3
rename to coder-apps/common/space_rocks_/static/media/die_spacerock.mp3
diff --git a/coder-base/static/apps/space_rocks_/media/die_spacerock.ogg b/coder-apps/common/space_rocks_/static/media/die_spacerock.ogg
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/die_spacerock.ogg
rename to coder-apps/common/space_rocks_/static/media/die_spacerock.ogg
diff --git a/coder-base/static/apps/space_rocks_/media/die_spacerock.wav b/coder-apps/common/space_rocks_/static/media/die_spacerock.wav
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/die_spacerock.wav
rename to coder-apps/common/space_rocks_/static/media/die_spacerock.wav
diff --git a/coder-base/static/apps/space_rocks_/media/spacerocks_shoot.mp3 b/coder-apps/common/space_rocks_/static/media/spacerocks_shoot.mp3
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/spacerocks_shoot.mp3
rename to coder-apps/common/space_rocks_/static/media/spacerocks_shoot.mp3
diff --git a/coder-base/static/apps/space_rocks_/media/spacerocks_shoot.ogg b/coder-apps/common/space_rocks_/static/media/spacerocks_shoot.ogg
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/spacerocks_shoot.ogg
rename to coder-apps/common/space_rocks_/static/media/spacerocks_shoot.ogg
diff --git a/coder-base/static/apps/space_rocks_/media/spacerocks_shoot.wav b/coder-apps/common/space_rocks_/static/media/spacerocks_shoot.wav
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/spacerocks_shoot.wav
rename to coder-apps/common/space_rocks_/static/media/spacerocks_shoot.wav
diff --git a/coder-base/static/apps/space_rocks_/media/thrust.mp3 b/coder-apps/common/space_rocks_/static/media/thrust.mp3
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/thrust.mp3
rename to coder-apps/common/space_rocks_/static/media/thrust.mp3
diff --git a/coder-base/static/apps/space_rocks_/media/thrust.ogg b/coder-apps/common/space_rocks_/static/media/thrust.ogg
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/thrust.ogg
rename to coder-apps/common/space_rocks_/static/media/thrust.ogg
diff --git a/coder-base/static/apps/space_rocks_/media/thrust.wav b/coder-apps/common/space_rocks_/static/media/thrust.wav
similarity index 100%
rename from coder-base/static/apps/space_rocks_/media/thrust.wav
rename to coder-apps/common/space_rocks_/static/media/thrust.wav
diff --git a/coder-base/views/apps/space_rocks_/index.html b/coder-apps/common/space_rocks_/views/index.html
similarity index 100%
rename from coder-base/views/apps/space_rocks_/index.html
rename to coder-apps/common/space_rocks_/views/index.html
diff --git a/coder-apps/install_app.sh b/coder-apps/install_app.sh
new file mode 100755
index 00000000..774a328a
--- /dev/null
+++ b/coder-apps/install_app.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+##
+## Copies an application from the coder-apps/[variant] directory to
+## the coder-base working directory.
+##
+## sh install_app appname base_path apps_path
+##
+## Eg.
+## sh install_app hello_coder ../coder-base/ ./common/
+
+if [ $# != 3 ]
+ then
+ echo -e "\nUse:\ninstall_app appname coderbase apppath\n"
+ exit
+fi
+
+app=$1
+base=$2
+from=$3
+
+mkdir "$base/apps/$app"
+mkdir "$base/static/apps/$app"
+mkdir "$base/static/apps/$app/js"
+mkdir "$base/static/apps/$app/css"
+mkdir "$base/static/apps/$app/media"
+mkdir "$base/views/apps/$app"
+
+cp $from/$app/app/* $base/apps/$app/
+cp $from/$app/views/* $base/views/apps/$app/
+cp $from/$app/static/js/* $base/static/apps/$app/js/
+cp $from/$app/static/css/* $base/static/apps/$app/css/
+cp $from/$app/static/media/* $base/static/apps/$app/media/
+
diff --git a/coder-apps/install_common.sh b/coder-apps/install_common.sh
new file mode 100755
index 00000000..a2ba85e8
--- /dev/null
+++ b/coder-apps/install_common.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+##
+## Copies the common platform apps to
+## the coder-base working directory.
+##
+## sh install_common base_path
+##
+## Eg.
+## sh install_common ../coder-base/
+
+if [ $# != 1 ]
+ then
+ echo -e "\nUse:\ninstall_common coderbase\n"
+ exit
+fi
+
+base=$1
+
+./install_app.sh auth $base ./common/
+./install_app.sh boilerplate $base ./common/
+./install_app.sh coder $base ./common/
+./install_app.sh coderlib $base ./common/
+./install_app.sh editor $base ./common/
+./install_app.sh eyeball $base ./common/
+./install_app.sh game2d $base ./common/
+./install_app.sh hello_coder $base ./common/
+./install_app.sh space_rocks_ $base ./common/
+
diff --git a/coder-apps/install_pi.sh b/coder-apps/install_pi.sh
new file mode 100755
index 00000000..9f9f4583
--- /dev/null
+++ b/coder-apps/install_pi.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+##
+## Copies the common platform and additional pi version apps to
+## the coder-base working directory.
+##
+## sh install_common base_path
+##
+## Eg.
+## sh install_common ../coder-base/
+
+if [ $# != 1 ]
+ then
+ echo -e "\nUse:\ninstall_common coderbase\n"
+ exit
+fi
+
+base=$1
+
+./install_common.sh $base
+
+./install_app.sh auth $base ./pi/
+./install_app.sh coder $base ./pi/
+./install_app.sh wifi $base ./pi/
+
diff --git a/coder-base/apps/auth/app.js b/coder-apps/pi/auth/app/app.js
similarity index 99%
rename from coder-base/apps/auth/app.js
rename to coder-apps/pi/auth/app/app.js
index 68822f5d..e5b7847b 100644
--- a/coder-base/apps/auth/app.js
+++ b/coder-apps/pi/auth/app/app.js
@@ -530,6 +530,7 @@ exports.api_logout_handler = function( req, res ) {
var saveDeviceSettings = function() {
err = fs.writeFileSync( process.cwd() + "/device.json", JSON.stringify(device_settings, null, 4), 'utf8' );
+ fs.chmodSync(process.cwd() + '/device.json', '600');
return err;
};
diff --git a/coder-apps/pi/auth/app/meta.json b/coder-apps/pi/auth/app/meta.json
new file mode 100644
index 00000000..521059b3
--- /dev/null
+++ b/coder-apps/pi/auth/app/meta.json
@@ -0,0 +1,8 @@
+{
+ "created": "2013-03-05",
+ "modified": "2013-08-18",
+ "color": "#1abc9c",
+ "author": "Jason Striegel",
+ "name": "Auth",
+ "hidden": true
+}
\ No newline at end of file
diff --git a/coder-apps/pi/auth/static/css/index.css b/coder-apps/pi/auth/static/css/index.css
new file mode 100644
index 00000000..2a1c6d98
--- /dev/null
+++ b/coder-apps/pi/auth/static/css/index.css
@@ -0,0 +1,133 @@
+
+body {
+ background-color: #f1c40f;
+ color: #fff;
+}
+
+.form {
+ text-align: center;
+ width: 400px;
+ left: 50%;
+ margin-top: 60px;
+ margin-left: -200px;
+ position: relative;
+}
+
+#animation {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+}
+
+.centercontainer {
+ display: table;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ top:0px;
+ left:0px;
+}
+.center {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+h1 {
+ font-size: 28px;
+ line-height: 28px;
+ font-weight: bold;
+ margin:0;
+ padding: 0 0 20px 0;
+}
+
+.instructions {
+ font-size: 21px;
+ line-height: 1.2em;
+ padding: 0 0 20px 0;
+}
+
+.formfield {
+ position: relative;
+ width: 400px;
+}
+.formfield.textinput .label {
+ position: absolute;
+ color: #999;
+ top:13px;
+ left:10px;
+ font-size: 16px;
+ line-height: 20px;
+ -webkit-font-smoothing: antialiased;
+}
+
+.formfield input[type=text], .formfield input[type=password] {
+ border: 2px solid transparent;
+ width: 376px;
+ padding: 10px 10px;
+ background-color: #fff;
+ color: #666;
+ height: 20px;
+ line-height: 16px;
+ font-size: 16px;
+ font-family: Arial, sans-serif;
+ border-radius: 3px;
+ -webkit-font-smoothing: antialiased;
+ margin-bottom: 24px;
+}
+
+.formfield input.error {
+ border-color: rgba(255,0,0,0.5);
+}
+
+.errormessage {
+ border: 2px solid transparent;
+ width: 376px;
+ padding: 10px 10px;
+ background-color: #e74c3c;
+ color: #ffffff;
+ min-height: 20px;
+ line-height: 20px;
+ font-size: 12px;
+ font-family: Arial, sans-serif;
+ border-radius: 3px;
+ -webkit-font-smoothing: antialiased;
+ margin-bottom: 24px;
+ text-transform: uppercase;
+}
+
+.submit {
+ font-size: 21px;
+ font-weight: bold;
+ line-height: 60px;
+ border-radius: 4px;
+ width:400px;
+ margin-bottom: 24px;
+ background-color: rgba(0,0,0,0.6);
+}
+.submit.disabled, .submit.disabled:hover {
+ background-color: rgba(0,0,0,0.2);
+}
+.submit:hover {
+ background-color: rgba(0,0,0,1);
+ cursor: pointer;
+}
+
+.cancel {
+ font-size: 21px;
+ font-weight: bold;
+ line-height: 60px;
+ border-radius: 4px;
+ width:400px;
+ margin-bottom: 24px;
+ background-color: rgba(0,0,0,0.6);
+}
+.cancel:hover {
+ background-color: rgba(0,0,0,1);
+ cursor: pointer;
+}
+
+
+
+
+
+
diff --git a/coder-apps/pi/auth/static/js/index.js b/coder-apps/pi/auth/static/js/index.js
new file mode 100644
index 00000000..e609a0fd
--- /dev/null
+++ b/coder-apps/pi/auth/static/js/index.js
@@ -0,0 +1,474 @@
+/**
+ * Coder for Raspberry Pi
+ * A simple platform for experimenting with web stuff.
+ * http://goo.gl/coder
+ *
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+$(document).ready( function() {
+
+ if ( pagemode === "logout" ) {
+ setupLogoutFields();
+ $('#logout_form').show();
+ } else if ( pagemode === "login" ) {
+ setupLoginFields();
+ $('#login_form').show();
+ } else if ( pagemode === "configure" ) {
+ setupConfigureFields();
+ $('#configure_form').show();
+ } else if ( pagemode === "addpassword" ) {
+ setupAddPasswordFields();
+ $('#addpassword_form').show();
+ } else if ( pagemode === "changepassword" ) {
+ setupChangePasswordFields();
+ $('#changepassword_form').show();
+ } else {
+ setupLoginFields();
+ $('#login_form').show();
+ }
+
+ buildAnimation();
+ updateAnimation();
+ $(window).on('resize', function() {
+ setTimeout( buildAnimation, 1 );
+ });
+});
+
+var hideTextLabel = function() {
+ $(this).parent().find('.label').hide();
+};
+var focusTextInput = function() {
+ $(this).parent().find('input').focus();
+};
+var onBlurTextInput = function() {
+ if ( $(this).val() == "" ) {
+ $(this).parent().find('.label').show();
+ }
+};
+var testLoginSubmitEnable = function() {
+ var $form = $('#login_form');
+ if ( pagemode === "logout" ) {
+ $form = $('#logout_form');
+ }
+
+
+ if ( $form.find('.pass').val() && $form.find('.pass').val() != "" ) {
+ $form.find('.submit').removeClass('disabled');
+ } else {
+ $form.find('.submit').addClass('disabled');
+ }
+};
+
+var testConfigureSubmitEnable = function() {
+ var $form = $('#configure_form');
+ var devicename = $form.find('.device_name').val();
+ if ( devicename && devicename != "" &&
+ isValidDeviceName(devicename) ) {
+ $form.find('.submit').removeClass('disabled');
+ } else {
+ $form.find('.submit').addClass('disabled');
+ }
+};
+
+var testAddPasswordSubmitEnable = function() {
+ var $form = $('#addpassword_form');
+ var pass = $form.find('.pass').val();
+ var pass_repeat = $form.find('.pass_repeat').val();
+ $form.find('.pass, .pass_repeat').removeClass('error');
+ $form.find('.errormessage').css('visibility','hidden');
+
+ if ( isValidPassword(pass) && pass === pass_repeat ) {
+ $form.find('.submit').removeClass('disabled');
+ } else {
+ $form.find('.submit').addClass('disabled');
+ }
+};
+
+var testChangePasswordSubmitEnable = function() {
+ var $form = $('#changepassword_form');
+ var oldpass = $form.find('.oldpass').val();
+ var pass = $form.find('.pass').val();
+ var pass_repeat = $form.find('.pass_repeat').val();
+ $form.find('.errormessage').css('visibility','hidden');
+ $form.find('.oldpass, .pass, .pass_repeat').removeClass('error');
+ if ( oldpass !== "" && isValidPassword(pass) && pass === pass_repeat ) {
+ $form.find('.submit').removeClass('disabled');
+ } else {
+ $form.find('.submit').addClass('disabled');
+ }
+};
+
+
+var setupLoginFields = function() {
+ $('#login_form .formfield.textinput .label').click( focusTextInput );
+ $('#login_form .formfield.textinput input').click( focusTextInput );
+ $('#login_form .formfield.textinput input').focus( hideTextLabel );
+ $('#login_form .formfield.textinput input').blur( onBlurTextInput );
+ $('#login_form .formfield.textinput input').change( testLoginSubmitEnable );
+ $('#login_form .formfield.textinput input').keydown( function(e) {
+ if (e.which != 13) {
+ setTimeout( testLoginSubmitEnable, 0 );
+ }
+ });
+
+ //submit on enter or button click
+ $('#login_form .formfield.textinput .pass').keypress(function (e) {
+ if (e.which == 13) {
+ e.preventDefault();
+ loginClick( $('#login_form .formfield.textinput .pass') );
+ }
+ });
+ $('#login_form .submit').click( function() {
+ loginClick( $('#login_form .formfield.textinput .pass') );
+ });
+};
+var setupLogoutFields = function() {
+ $('#logout_form .formfield.textinput .label').click( focusTextInput );
+ $('#logout_form .formfield.textinput input').click( focusTextInput );
+ $('#logout_form .formfield.textinput input').focus( hideTextLabel );
+ $('#logout_form .formfield.textinput input').blur( onBlurTextInput );
+ $('#logout_form .formfield.textinput input').change( testLoginSubmitEnable );
+ $('#logout_form .formfield.textinput input').keydown( function(e) {
+ if (e.which != 13) {
+ setTimeout( testLoginSubmitEnable, 0 );
+ }
+ });
+
+ //submit on enter or button click
+ $('#logout_form .formfield.textinput .pass').keypress(function (e) {
+ if (e.which == 13) {
+ e.preventDefault();
+ loginClick( $('#logout_form .formfield.textinput .pass') );
+ }
+ });
+ $('#logout_form .submit').click( function() {
+ loginClick( $('#logout_form .formfield.textinput .pass') );
+ });
+};
+var setupConfigureFields = function() {
+ $('#configure_form .formfield.textinput .label').click( focusTextInput );
+ $('#configure_form .formfield.textinput input').click( focusTextInput );
+ $('#configure_form .formfield.textinput input').focus( hideTextLabel );
+ $('#configure_form .formfield.textinput input').blur( onBlurTextInput );
+ $('#configure_form .formfield.textinput input').change( testConfigureSubmitEnable );
+ $('#configure_form .formfield.textinput input').keydown( function(e) {
+ if (e.which != 13) {
+ setTimeout( testConfigureSubmitEnable, 0 );
+ }
+ });
+ $('#configure_form .submit').click( configureClick );
+ $('#configure_form .device_name').val( 'My Coder' ).parent().find('.label').hide();
+ testConfigureSubmitEnable();
+};
+var setupAddPasswordFields = function() {
+ $('#addpassword_form .formfield.textinput .label').click( focusTextInput );
+ $('#addpassword_form .formfield.textinput input').click( focusTextInput );
+ $('#addpassword_form .formfield.textinput input').focus( hideTextLabel );
+ $('#addpassword_form .formfield.textinput input').blur( onBlurTextInput );
+ $('#addpassword_form .formfield.textinput input').change( testAddPasswordSubmitEnable );
+ $('#addpassword_form .formfield.textinput input').keydown( function(e) {
+ if (e.which != 13) {
+ setTimeout( testAddPasswordSubmitEnable, 0 );
+ }
+ });
+
+ $('#addpassword_form .formfield.textinput .pass_repeat').keypress(function (e) {
+ if (e.which == 13) {
+ e.preventDefault();
+ addPasswordClick();
+ }
+ });
+
+ $('#addpassword_form .submit').click( addPasswordClick );
+};
+
+var setupChangePasswordFields = function() {
+ $('#changepassword_form .formfield.textinput .label').click( focusTextInput );
+ $('#changepassword_form .formfield.textinput input').click( focusTextInput );
+ $('#changepassword_form .formfield.textinput input').focus( hideTextLabel );
+ $('#changepassword_form .formfield.textinput input').blur( onBlurTextInput );
+ $('#changepassword_form .formfield.textinput input').change( testChangePasswordSubmitEnable );
+ $('#changepassword_form .formfield.textinput input').keydown( function(e) {
+ if (e.which != 13) {
+ setTimeout( testChangePasswordSubmitEnable, 0 );
+ }
+ });
+
+ $('#changepassword_form .formfield.textinput .pass_repeat').keypress(function (e) {
+ if (e.which == 13) {
+ e.preventDefault();
+ changePasswordClick();
+ }
+ });
+
+ $('#changepassword_form .submit').click( changePasswordClick );
+ $('#changepassword_form .cancel').click( function() {
+ window.location.href="http://github.com/";
+ });
+};
+
+var loginClick = function( what ) {
+ $this = $(what);
+ var $form = $('#login_form');
+ if ( pagemode === "logout" ) {
+ $form = $('#logout_form');
+ }
+
+ $form.find('.errormessage').css('visibility','hidden');
+ $form.find('.pass').removeClass('error');
+ $.post(
+ appurl + '/api/login',
+ {
+ password: $this.parent().find('.pass').val()
+ },
+ function( data ) {
+ console.log( data );
+ if( data.status === "success" ) {
+ var firstuse = "";
+ if ( typeof getParams['firstuse'] !== 'undefined' ) {
+ firstuse = '?firstuse';
+ }
+ window.location.href="http://github.com/app/coder" + firstuse;
+ } else {
+ $form.find('.errormessage').text( data.error ).css('visibility','visible');
+ $form.find('.pass').addClass('error');
+ }
+ }
+ );
+};
+
+var configureClick = function() {
+ $this = $(this);
+ var $form = $('#configure_form');
+
+ $form.find('.device_name').removeClass('error');
+ var devicename = $this.parent().find('.device_name').val();
+
+ if ( !isValidDeviceName(devicename) ) {
+ $form.find('.device_name').addClass('error');
+ return;
+ }
+
+ $.post(
+ appurl + '/api/devicename/set',
+ {
+ device_name: devicename
+ },
+ function( data ) {
+ console.log( data );
+ if( data.status === "success" ) {
+ window.location.href="http://github.com/app/auth";
+ } else {
+ $form.find('.device_name').addClass('error');
+ }
+ }
+ );
+};
+
+var addPasswordClick = function() {
+ var $form = $('#addpassword_form');
+
+ $form.find('.pass, .pass_repeat').removeClass('error');
+ $form.find('.errormessage').css('visibility','hidden');
+ var pass = $form.find('.pass').val();
+ var pass_repeat = $form.find('.pass_repeat').val();
+
+ if ( !isValidPassword(pass) ) {
+ $form.find('.pass').addClass('error');
+ $form.find('.errormessage').text( getPasswordProblem(pass) ).css('visibility','visible');
+ return;
+ }
+ if ( pass !== pass_repeat ) {
+ $form.find('.pass_repeat').addClass('error');
+ $form.find('.errormessage').text( "new password does not match" ).css('visibility','visible');
+ return;
+ }
+
+ $.post(
+ appurl + '/api/addpassword',
+ {
+ password: pass
+ },
+ function( data ) {
+ console.log( data );
+ if( data.status === "success" ) {
+ var firstuse = '';
+ if ( typeof getParams['firstuse'] !== 'undefined' ) {
+ firstuse = '?firstuse';
+ }
+ window.location.href="http://github.com/app/auth" + firstuse;
+ } else {
+ $form.find('.pass').addClass('error');
+ $form.find('.errormessage').text( data.error ).css('visibility','visible');
+ }
+ }
+ );
+};
+
+
+var changePasswordClick = function() {
+
+ var $form = $('#changepassword_form');
+
+ $form.find('.oldpass, .pass, .pass_repeat').removeClass('error');
+ $form.find('.errormessage').css('visibility','hidden');
+ var oldpass = $form.find('.oldpass').val();
+ var pass = $form.find('.pass').val();
+ var pass_repeat = $form.find('.pass_repeat').val();
+
+ if ( oldpass === "" ) {
+ $form.find('.oldpass').addClass('error');
+ $form.find('.errormessage').text( "your current password is required" ).css('visibility','visible');
+ return;
+ }
+ if ( !isValidPassword(pass) ) {
+ $form.find('.pass').addClass('error');
+ $form.find('.errormessage').text( getPasswordProblem(pass) ).css('visibility','visible');
+ return;
+ }
+ if ( pass !== pass_repeat ) {
+ $form.find('.pass_repeat').addClass('error');
+ $form.find('.errormessage').text( "new password does not match" ).css('visibility','visible');
+ return;
+ }
+
+ $.post(
+ appurl + '/api/changepassword',
+ {
+ oldpassword: oldpass,
+ password: pass
+ },
+ function( data ) {
+ console.log( data );
+ if( data.status === "success" ) {
+ window.location.href="http://github.com/app/auth";
+ } else {
+ $form.find('.oldpass').addClass('error');
+ $form.find('.errormessage').text( data.error ).css('visibility','visible');
+ }
+ }
+ );
+};
+
+var isValidDeviceName = function( name ) {
+ if ( !name || name === '' ) {
+ return false;
+ }
+ //starts with an ascii word char. can contain word char's spaces and '
+ if ( !name.match(/^[a-zA-Z0-9][\w ']*$/) ) {
+ return false;
+ }
+ //ends in an ascii word char
+ if ( !name.match(/[a-zA-Z0-9]$/) ) {
+ return false;
+ }
+ return true;
+};
+
+var getPasswordProblem = function( pass ) {
+ if ( !pass || pass === '' ) {
+ return "the password is empty";
+ }
+ if ( pass.length < 6 ) {
+ return "the password should contain at least 6 characters";
+ }
+ if ( !pass.match(/[a-z]/) ||
+ !pass.match(/[A-Z0-9\-\_\.\,\;\:\'\"\[\]\{\}\!\@\#\$\%\^\&\*\(\)\\].*[A-Z0-9\-\_\.\,\;\:\'\"\[\]\{\}\!\@\#\$\%\^\&\*\(\)\\]/) ) {
+ return "your password must contain a lower case letter and at least two upper case letters or numbers";
+ }
+};
+var isValidPassword = function( pass ) {
+ if ( !pass || pass === '' ) {
+ return false;
+ }
+ //at least 6 characters
+ if ( pass.length < 6 ) {
+ return false;
+ }
+ //contains lower case
+ if ( !pass.match(/[a-z]/) ) {
+ return false;
+ }
+ //contains two upper case or numbers
+ if ( !pass.match(/[A-Z0-9\-\_\.\,\;\:\'\"\[\]\{\}\!\@\#\$\%\^\&\*\(\)\\].*[A-Z0-9\-\_\.\,\;\:\'\"\[\]\{\}\!\@\#\$\%\^\&\*\(\)\\]/) ) {
+ return false;
+ }
+ return true;
+};
+
+var circles = [];
+var $canvas;
+var ctx;
+var buildAnimation = function() {
+ circles = [];
+ $canvas = $("#animation");
+ ctx = $canvas.get(0).getContext("2d");
+ var w = $canvas.parent().width();
+ var h = $canvas.parent().height();
+ $canvas.attr('width', w);
+ $canvas.attr('height', h);
+
+ for ( var x=0; x<20; x++ ) {
+ var sx = (Math.random() * (w+100)) - 50;
+ var sy = (Math.random() * 800) - 400;
+ var circle = {
+ sx: sx,
+ sy: sy,
+ x: sx,
+ y: sy,
+ r: (Math.random() * 150) + 30,
+ opacity: .2,
+ direction: Math.random() > .5? 1:-1
+ };
+ circles.push( circle );
+ }
+};
+
+var updateAnimation = function() {
+ ctx.clearRect(0, 0, $canvas.width(), $canvas.height());
+ for ( var x=0; x .9 ) {
+ circle.opacity = .9;
+ }
+
+ circle.x += (Math.random() * .2) * circle.direction;
+ if ( circle.x < circle.sx - 250 ) {
+ circle.x = circle.sx - 250;
+ circle.direction=1;
+ } else if ( circle.x > circle.sx + 250 ) {
+ circle.x = circle.sx + 250;
+ circle.direction = -1;
+ }
+
+
+ }
+ setTimeout( updateAnimation, 1000/60 );
+};
+
+
+
diff --git a/coder-apps/pi/auth/static/media/.gitignore b/coder-apps/pi/auth/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/views/apps/auth/index.html b/coder-apps/pi/auth/views/index.html
similarity index 100%
rename from coder-base/views/apps/auth/index.html
rename to coder-apps/pi/auth/views/index.html
diff --git a/coder-apps/pi/coder/app/app.js b/coder-apps/pi/coder/app/app.js
new file mode 100644
index 00000000..db13d8ec
--- /dev/null
+++ b/coder-apps/pi/coder/app/app.js
@@ -0,0 +1,495 @@
+/**
+ * Coder for Raspberry Pi
+ * A simple platform for experimenting with web stuff.
+ * http://goo.gl/coder
+ *
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var mustache = require('mustache');
+var util = require('util');
+var fs = require('fs');
+var spawn = require('child_process').spawn;
+
+//hack to make fs.existsSync work between different node versions
+if ( !fs.existsSync ) {
+ var path = require('path');
+ fs.existsSync = path.existsSync;
+}
+
+exports.settings={};
+//These are dynamically updated by the runtime
+//settings.appname - the app id (folder) where your app is installed
+//settings.viewpath - prefix to where your view html files are located
+//settings.staticurl - base url path to static assets /static/apps/appname
+//settings.appurl - base url path to this app /app/appname
+//settings.device_name
+//settings.coder_owner
+//settings.coder_color
+
+exports.get_routes = [
+ { path:'/', handler:'index_handler' },
+ { path: /^\/export\/download\/(\w+\.zip)$/, handler:'export_download_handler' },
+];
+
+
+exports.post_routes = [
+ { path: '/api/app/create', handler:'api_app_create_handler' },
+ { path: /^\/api\/app\/remove\/(\w+)$/, handler:'api_app_remove_handler' },
+ { path: /^\/api\/app\/export\/(\w+)$/, handler:'api_app_export_handler' },
+ { path: /^\/api\/app\/import$/, handler:'api_app_import_handler' },
+];
+
+exports.on_destroy = function() {
+};
+
+exports.index_handler = function( req, res ) {
+ var tmplvars = {};
+ tmplvars['static_url'] = exports.settings.staticurl;
+ tmplvars['app_name'] = exports.settings.appname;
+ tmplvars['app_url'] = exports.settings.appurl;
+ tmplvars['device_name'] = exports.settings.device_name;
+ tmplvars['coder_color'] = exports.settings.coder_color;
+ tmplvars['coder_owner'] = exports.settings.coder_owner;
+
+ res.render( exports.settings.viewpath + '/index', tmplvars );
+};
+
+var getAppIDFromTitle = function( apptitle ) {
+ var newappid = apptitle.toLowerCase();
+ newappid = newappid.replace(/\./g, "_");
+ newappid = newappid.replace(/[^\w]/g, "_");
+ newappid = newappid.replace(/_+/g, "_");
+ return newappid;
+};
+
+var getAvailableNewAppID = function( newappid ) {
+ //scan for an available id if this one exists already
+ var idavailable = false;
+ var iteration = 0;
+ var appdir = process.cwd() + "/apps/";
+ var allfiles = fs.readdirSync(appdir);
+ while ( !idavailable ) {
+ var potential = newappid;
+ if ( iteration > 0 ) {
+ potential = potential + '_' + iteration;
+ }
+ if ( allfiles.indexOf( potential ) >= 0 ) {
+ iteration++;
+ } else {
+ newappid = potential;
+ idavailable = true;
+ }
+ }
+ return newappid;
+};
+
+exports.api_app_create_handler = function( req, res ) {
+
+ var apptitle = req.param('app_title');
+ var appcolor = req.param('app_color');
+
+ if ( typeof apptitle === 'undefined' || apptitle === ""
+ || typeof appcolor === 'undefined' || appcolor === "" ) {
+ res.json({
+ status: 'error',
+ error: 'invalid parameters'
+ });
+ return;
+ }
+
+ var newappid = getAppIDFromTitle( apptitle );
+
+ if ( newappid === "" ) {
+ res.json({
+ status: 'error',
+ error: 'invalid app id'
+ });
+ }
+
+ newappid = getAvailableNewAppID( newappid );
+
+ buildFolderStructure( newappid );
+ cloneApp( 'boilerplate', newappid );
+
+ var metainfo = {
+ created: getDateString( new Date() ),
+ modified: getDateString( new Date() ),
+ color: appcolor,
+ author: exports.settings.coder_owner,
+ name: apptitle,
+ hidden: false,
+ };
+ var metapath = process.cwd() + '/apps/' + newappid + '/meta.json';
+ fs.writeFileSync( metapath, JSON.stringify(metainfo, null, 4), 'utf8' );
+
+ res.json({
+ status: 'success',
+ appname: newappid
+ });
+
+};
+
+exports.export_download_handler = function( req, res, pathmatches ) {
+
+ var exportname = "export.zip";
+ if ( pathmatches && pathmatches[1] !== "" ) {
+ exportname = pathmatches[1];
+ } else {
+ res.json({
+ status: 'error',
+ error: 'invalid parameters'
+ });
+ return;
+ }
+
+ var exportfile = 'appexport.zip';
+ var path = process.cwd();
+ if ( !fs.existsSync( path + '/tmp/' + exportfile ) ) {
+ res.json({
+ status: "error",
+ error: "Export file doesn't exist"
+ });
+ return;
+ }
+
+ res.download( path + '/tmp/' + exportfile, exportname );
+
+
+};
+
+
+exports.api_app_import_handler = function( req, res, pathmatches ) {
+
+ if ( !req.files || !req.files['import_file'] ) {
+ res.json({
+ status: 'error',
+ error: 'invalid parameters'
+ });
+ return;
+ }
+
+ if ( !req.files['import_file'].type == 'application/zip' ) {
+ res.json({
+ status: 'error',
+ error: 'invalid file type'
+ });
+ return;
+ }
+
+ var path = process.cwd();
+ var success = true;
+ var importkey = 'appimport'; //TODO: maybe this should be random and auto-cleaned
+ var tmpfolder = path + '/tmp/' + importkey;
+ try { forceRemoveDir( tmpfolder ); } catch (e) {}
+ try { fs.mkdirSync( tmpfolder ); } catch (e) { success = false; }
+
+
+ var completeImport = function() {
+
+ if ( !fs.existsSync( tmpfolder + '/app/meta.json' )
+ || !fs.existsSync( tmpfolder + '/app/app.js')
+ || !fs.existsSync( tmpfolder + '/views/index.html')
+ || !fs.existsSync( tmpfolder + '/static/css/index.css')
+ || !fs.existsSync( tmpfolder + '/static/js/index.js') ) {
+
+ res.json({
+ status: "error",
+ error: "Invalid application bundle"
+ });
+ return;
+ }
+
+
+ var importfile = fs.readFileSync( tmpfolder + '/app/meta.json', 'utf-8' );
+ var importinfo = JSON.parse(importfile);
+
+ if ( !importinfo || !importinfo.color
+ || typeof(importinfo.author) === 'undefined' || !importinfo.name
+ || !importinfo.created || !importinfo.modified ) {
+
+ res.json({
+ status: "error",
+ error: "invalid project file"
+ });
+ return;
+ }
+
+
+ var metainfo = {
+ created: importinfo.created,
+ modified: importinfo.modified,
+ color: importinfo.color,
+ author: importinfo.author,
+ name: importinfo.name,
+ hidden: false,
+ };
+
+ var newappid = getAppIDFromTitle( metainfo.name );
+ if ( newappid === "" ) {
+ res.json({
+ status: 'error',
+ error: 'invalid app id'
+ });
+ }
+ newappid = getAvailableNewAppID( newappid );
+ buildFolderStructure( newappid );
+
+ //app meta.json file
+ var metapath = process.cwd() + '/apps/' + newappid + '/meta.json';
+ fs.writeFileSync( metapath, JSON.stringify(metainfo, null, 4), 'utf8' );
+
+ //app node.js file
+ copyFile( tmpfolder + '/app/app.js', path + '/apps/' + newappid + '/app.js' );
+ //html view
+ copyFile( tmpfolder + '/views/index.html', path + '/views/apps/' + newappid + '/index.html' );
+ //css data
+ copyFile( tmpfolder + '/static/css/index.css', path + '/static/apps/' + newappid + '/css/index.css' );
+ //index.js file
+ copyFile( tmpfolder + '/static/js/index.js', path + '/static/apps/' + newappid + '/js/index.js' );
+
+ var mediadir = tmpfolder + '/static/media/';
+ var mediafiles = fs.readdirSync( mediadir );
+ for ( var x in mediafiles ) {
+ var filename = mediafiles[x];
+ var info = fs.statSync( mediadir + filename );
+ if ( typeof info !== 'undefined' && info && info.isFile() ) {
+ copyFile( mediadir + filename, path + "/static/apps/" + newappid + "/media/" + filename );
+ }
+ }
+
+
+ res.json({
+ status: "success",
+ name: metainfo.name,
+ appname: newappid
+ });
+ };
+
+
+ var uploadPath = tmpfolder + '/appimport.zip';
+ fs.readFile(req.files['import_file'].path, function (err, data) {
+ fs.writeFile(uploadPath, data, function (err) {
+
+ var unzip = spawn('unzip', ['appimport.zip'], { cwd: tmpfolder });
+ unzip.stdout.on('data', function (data) {
+ });
+ unzip.stderr.on('data', function (data) {
+ });
+ unzip.on('exit', function (code) {
+ if(code !== 0) {
+ res.json({
+ status: "error",
+ error: "unzip error: " + code
+ });
+ } else {
+ completeImport();
+ }
+ });
+
+ });
+ });
+
+
+
+};
+
+exports.api_app_export_handler = function( req, res, pathmatches ) {
+
+ var apptoexport = "";
+ if ( pathmatches && pathmatches[1] !== "" ) {
+ apptoexport = pathmatches[1];
+ } else {
+ res.json({
+ status: 'error',
+ error: 'invalid parameters'
+ });
+ return;
+ }
+
+ var path = process.cwd();
+ if ( !fs.existsSync( path + '/apps/' + apptoexport + '/app.js' ) ) {
+ res.json({
+ status: "error",
+ error: "Application doesn't exist"
+ });
+ return;
+ }
+
+ var success = true;
+ var exportkey = 'appexport'; //TODO: maybe this should be random and auto-cleaned
+ var tmpfolder = path + '/tmp/' + exportkey;
+ try { forceRemoveDir( tmpfolder ); } catch (e) {}
+ try { fs.unlinkSync( path + '/tmp/' + exportkey + '.zip' ); } catch (e) {}
+ try { fs.mkdirSync( tmpfolder ); } catch (e) { success = false; }
+ try { fs.mkdirSync( tmpfolder + '/app' ); } catch (e) { success = false; }
+ try { fs.mkdirSync( tmpfolder + '/static' ); } catch (e) { success = false; }
+ try { fs.mkdirSync( tmpfolder + '/static/css' ); } catch (e) { success = false; }
+ try { fs.mkdirSync( tmpfolder + '/static/js' ); } catch (e) { success = false; }
+ try { fs.mkdirSync( tmpfolder + '/static/media' ); } catch (e) { success = false; }
+ try { fs.mkdirSync( tmpfolder + '/views' ); } catch (e) { success = false; }
+
+ if ( !success ) {
+ res.json({
+ status: "error",
+ error: "Cannot create export directory."
+ });
+ return;
+ }
+
+
+ //app node.js file
+ copyFile( path + '/apps/' + apptoexport + '/app.js', tmpfolder + '/app/app.js' );
+ //app meta.json file
+ copyFile( path + '/apps/' + apptoexport + '/meta.json', tmpfolder + '/app/meta.json' );
+ //html view
+ copyFile( path + '/views/apps/' + apptoexport + '/index.html', tmpfolder + '/views/index.html' );
+ //css data
+ copyFile( path + '/static/apps/' + apptoexport + '/css/index.css', tmpfolder + '/static/css/index.css' );
+ //index.js file
+ copyFile( path + '/static/apps/' + apptoexport + '/js/index.js', tmpfolder + '/static/js/index.js' );
+
+ var mediadir = path + "/static/apps/" + apptoexport + "/media/";
+ var mediafiles = fs.readdirSync( mediadir );
+ for ( var x in mediafiles ) {
+ var filename = mediafiles[x];
+ var info = fs.statSync( mediadir + filename );
+ if ( typeof info !== 'undefined' && info && info.isFile() ) {
+ copyFile( mediadir + filename, tmpfolder + '/static/media/' + filename );
+ }
+ }
+
+ var zip = spawn('zip', ['-r', '../appexport.zip', '.', '-i', '*'], { cwd: tmpfolder });
+ zip.stdout.on('data', function (data) {
+ //console.log('coder::api_app_export_handler zip: ' + data );
+ });
+ zip.stderr.on('data', function (data) {
+ //console.log('coder::api_app_export_handler zip error: ' + data );
+ });
+ zip.on('exit', function (code) {
+ if(code !== 0) {
+ res.json({
+ status: "error",
+ error: "zip error: " + code
+ });
+ } else {
+ res.json({
+ status: "success",
+ file: apptoexport + ".zip"
+ });
+ }
+ });
+
+};
+
+exports.api_app_remove_handler = function( req, res, pathmatches ) {
+ var apptoremove = "";
+ if ( pathmatches && pathmatches[1] !== "" ) {
+ apptoremove = pathmatches[1];
+ } else {
+ res.json({
+ status: 'error',
+ error: 'invalid parameters'
+ });
+ return;
+ }
+
+ var path = process.cwd();
+ if ( !fs.existsSync( path + '/apps/' + apptoremove + '/app.js' ) ) {
+ res.json({
+ status: "error",
+ error: "Application doesn't exist"
+ });
+ return;
+ }
+
+ try { forceRemoveDir( path + '/static/apps/' + apptoremove ); } catch (e) {}
+ try { forceRemoveDir( path + '/views/apps/' + apptoremove ); } catch (e) {}
+ try { forceRemoveDir( path + '/apps/' + apptoremove ); } catch (e) {}
+
+ res.json({
+ status: "success",
+ data: "Application " + apptoremove + " removed."
+ });
+
+};
+
+
+var copyFile = function( from, to ) {
+ //cpdata = fs.readFileSync( from, 'utf-8' );
+ //fs.writeFileSync( to, cpdata, 'utf-8' );
+ cpdata = fs.readFileSync( from );
+ fs.writeFileSync( to, cpdata );
+};
+
+
+var buildFolderStructure = function( appid ) {
+ var path = process.cwd();
+ var success = true;
+ try { fs.mkdirSync( path + '/static/apps/' + appid ); } catch (e) { success = false; }
+ try { fs.mkdirSync( path + '/static/apps/' + appid + '/css' ); } catch (e) { success = false; }
+ try { fs.mkdirSync( path + '/static/apps/' + appid + '/js' ); } catch (e) { success = false; }
+ try { fs.mkdirSync( path + '/static/apps/' + appid + '/media' ); } catch (e) { success = false; }
+ try { fs.mkdirSync( path + '/views/apps/' + appid ); } catch (e) { success = false; }
+ try { fs.mkdirSync( path + '/apps/' + appid ); } catch (e) { success = false; }
+ return success;
+};
+
+var cloneApp = function( fromapp, toapp ) {
+ var path = process.cwd();
+ var cpdata = "";
+
+ //app node.js file
+ copyFile( path + '/apps/' + fromapp + '/app.js', path + '/apps/' + toapp + '/app.js' );
+ //html view
+ copyFile( path + '/views/apps/' + fromapp + '/index.html', path + '/views/apps/' + toapp + '/index.html' );
+ //css data
+ copyFile( path + '/static/apps/' + fromapp + '/css/index.css', path + '/static/apps/' + toapp + '/css/index.css' );
+ //index.js file
+ copyFile( path + '/static/apps/' + fromapp + '/js/index.js', path + '/static/apps/' + toapp + '/js/index.js' );
+
+ //TODO: not currently cloning anything in media directory.
+};
+
+
+var getDateString = function( d ) {
+ var now = new Date();
+ var twodigits = function( x ) {
+ return x<10 ? '0' + x: x;
+ };
+ return d.getFullYear() + "-" + twodigits(d.getMonth()+1) + '-' + twodigits(d.getDate());
+};
+
+//recursively delete a directory
+var forceRemoveDir = function( path ) {
+ util.log( 'PURGING ' + path );
+ try {
+ var contents = fs.readdirSync(path);
+ for (var i = 0; i < contents.length; i++) {
+ var fp = path + "/" + contents[i];
+ if ( fs.statSync( fp ).isDirectory() ) {
+ forceRemoveDir( fp );
+ } else {
+ //util.log( 'delete ' + fp );
+ fs.unlinkSync( fp );
+ }
+ }
+ }
+ catch(e)
+ {
+ }
+ //util.log( 'remove directory ' + path );
+ fs.rmdirSync( path );
+};
diff --git a/coder-apps/pi/coder/app/meta.json b/coder-apps/pi/coder/app/meta.json
new file mode 100644
index 00000000..24036299
--- /dev/null
+++ b/coder-apps/pi/coder/app/meta.json
@@ -0,0 +1,8 @@
+{
+ "created": "2013-03-05",
+ "modified": "2014-01-07",
+ "color": "#bdc3c7",
+ "author": "Jason Striegel",
+ "name": "Coder",
+ "hidden": true
+}
\ No newline at end of file
diff --git a/coder-apps/pi/coder/static/css/index.css b/coder-apps/pi/coder/static/css/index.css
new file mode 100644
index 00000000..80fecd33
--- /dev/null
+++ b/coder-apps/pi/coder/static/css/index.css
@@ -0,0 +1,499 @@
+
+
+
+
+#additem {
+ background-color: #2ecc71;
+}
+
+#applist {
+ line-height:0;
+ padding: 0 0px 24px 24px;
+ position: absolute;
+ top: 60px;
+ bottom: 0px;
+ overflow: auto;
+}
+
+.appitem {
+ display: inline-block;
+ width: 270px;
+ height: 160px;
+ margin: 24px 24px 0 0;
+ position: relative;
+ cursor: pointer;
+}
+
+.appitem .textcenter {
+ display: table;
+ height: 100%;
+ width: 100%;
+ position: absolute;
+}
+.appitem h2 {
+ display: table-cell;
+ vertical-align: middle;
+ text-align: center;
+ font-size:24px;
+ line-height:24px;
+ padding: 1em;
+ line-height: 1em;
+ color: #fff;
+}
+
+.appitem .editbutton {
+ color: #fff;
+ font-size: 16px;
+ line-height: 16px;
+ margin:0;
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ padding: 12px;
+ opacity: 0.5;
+ font-weight: bold;
+}
+.appitem .editbutton:hover {
+ opacity: 1;
+ cursor: pointer;
+}
+
+#addapp_button {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+}
+#addapp_icon {
+ height: 40px;
+ width: 40px;
+ left: 50%;
+ top: 50%;
+ margin-left: -20px;
+ margin-top: -20px;
+ background-color: transparent;
+ /*border-radius: 4px;*/
+ position: absolute;
+ background-image: url(http://github.com/static/common/media/coder_icons.png);
+ background-position: -167px 0px;
+ opacity: 0.8;
+}
+#addapp_button:hover #addapp_icon {
+ /*background-color: rgba(255,255,255,0.1);*/
+ opacity: 1;
+ cursor: pointer;
+}
+
+
+
+#createform {
+ position: absolute;
+ padding: 16px 0 0 16px;
+ width: 254px;
+ height: 144px;
+}
+
+
+.formfield {
+ position: relative;
+ margin: 0 0 8px 0;
+}
+.formfield.textinput .label {
+ position: absolute;
+ color: #999;
+ top:10px;
+ left:12px;
+ font-size: 16px;
+ line-height: 18px;
+ -webkit-font-smoothing: antialiased;
+}
+
+.formfield input[type=text], .formfield input[type=password] {
+ border: 2px solid transparent;
+ width: 212px;
+ padding: 8px 10px;
+ background-color: #fff;
+ color: #666;
+ height: 18px;
+ line-height: 18px;
+ font-size: 16px;
+ font-family: Arial, sans-serif;
+ border-radius: 3px;
+ -webkit-font-smoothing: antialiased;
+}
+
+.formfield input.error, .formfield.selectbox .selectbg.error {
+ border-color: rgba(255,0,0,0.5);
+}
+
+
+#additem .colorchit {
+ width: 12px;
+ height: 12px;
+ border: 2px solid transparent;
+ display: block;
+ float: left;
+ border-radius: 4px;
+ margin: 4px 4px 0 0;
+}
+#additem .colorchit.active,
+#additem .colorchit.active:hover {
+ border-color: #fff;
+}
+#additem .colorchit:hover {
+ border-color: rgba(255,255,255,.6);
+ cursor: pointer;
+}
+
+
+
+#createform .submit,
+#createform .cancel,
+#createform .import {
+ position: absolute;
+ bottom: 16px;
+ width: 70px;
+ padding: 5px;
+ height: 20px;
+ text-align: center;
+ display: block;
+ border-radius: 4px;
+ z-index: 101;
+ font-size: 14px;
+ line-height: 20px;
+ font-family: Arial, sans-serif;
+ color: #000;
+ background-color: #fff;
+ opacity: 0.8;
+}
+#createform .submit:hover,
+#createform .cancel:hover,
+#createform .import:hover {
+ opacity: 1;
+ cursor: pointer;
+}
+#createform .submit {
+ left: 16px;
+}
+#createform .cancel {
+ /*opacity: 0.4;*/
+ left: 100px;
+}
+#createform .import {
+ right: 16px;
+ width: 20px;
+ overflow: hidden;
+ background-image: url(http://github.com/static/common/media/coder_icons.png);
+ background-position: -227px -5px;
+ background-color: transparent;
+}
+
+#import_file {
+ position: absolute;
+ top: 0;
+ left: 0;
+ opacity: 0;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ border: 0;
+ background-color: #f00;
+ cursor: pointer;
+}
+#import_file:hover {
+ cursor: pointer;
+}
+
+
+.buttonlabel {
+ display: block;
+ width: 40px;
+ height: 40px;
+ padding: 0px;
+ margin: 9px 15px 0 0;
+ line-height: 30px;
+ color: #aaaaaa;
+ border-radius: 4px;
+ border: 1px solid transparent;
+ font-size: 16px;
+ text-align:center;
+ z-index: 1;
+ position: absolute;
+ text-transform: uppercase;
+ vertical-align: top;
+ background-image: url(http://github.com/static/common/media/coder_icons.png);
+}
+.buttonlabel:hover {
+ background-color: rgba( 255,255,255,0.2 );
+ cursor: pointer;
+}
+#settings_button {
+ background-position: -59px 0px;
+ top:0px;
+ right:0px;
+}
+.settingsEnabled #settings_button {
+ background-color: rgba( 255,255,255,.2 );
+ border-color: rgba( 255,255,255,.4 );
+}
+
+
+#settingscontainer {
+ min-height: 100%;
+}
+
+.sidepanel {
+ position: fixed;
+ width: 300px;
+ top: 0px;
+ min-height: 100%;
+ right: 0;
+ z-index:1;
+ display: none;
+ background-color: #414141;
+}
+
+.settingsEnabled #settingscontainer {
+ display: block;
+}
+.settingsEnabled #applist {
+ width: auto;
+ left: 0px;
+ right: 300px;
+ position: absolute;
+}
+.settingsEnabled #coder_nav {
+ width: auto;
+ left: 0px;
+ right: 300px;
+}
+
+.sidepanel h1 {
+ margin:0;
+ padding: 0px 0px 0px 20px;
+ display: block;
+ height: 60px;
+ line-height: 60px;
+ color: #fff;
+ font-size: 24px;
+ background-color: #2b2b2b;
+}
+
+.sidepanel .save, .sidepanel .cancel {
+ position: absolute;
+ bottom: 24px;
+ width: 70px;
+ padding: 5px;
+ height: 20px;
+ text-align: center;
+ display: inline-block;
+ background-color: #333;
+ border-radius: 4px;
+ color: #fff;
+ z-index: 101;
+ font-size: 14px;
+ line-height: 20px;
+ display:none;
+ font-family: Arial, sans-serif;
+}
+.sidepanel .save, .sidepanel .cancel {
+ color: #333;
+ background-color: rgba( 255,255,255, 0.8 );
+}
+.sidepanel .save:hover, .sidepanel .cancel:hover {
+ background-color: #fff;
+ cursor: pointer;
+}
+.sidepanel.changed .save, .sidepanel.changed .cancel {
+ display: block;
+}
+
+
+
+.sidepanel .save {
+ left: 24px;
+}
+.sidepanel .cancel {
+ left: 110px;
+}
+
+
+.sidepanel .logout {
+ position: absolute;
+ top: 15px;
+ right: 24px;
+ width: 70px;
+ padding: 5px;
+ height: 20px;
+ text-align: center;
+ display: inline-block;
+ background-color: #333;
+ border-radius: 4px;
+ color: #fff;
+ z-index: 101;
+ font-size: 14px;
+ line-height: 20px;
+ font-family: Arial, sans-serif;
+ color: #333;
+ background-color: rgba( 255,255,255, 0.8 );
+}
+.sidepanel .logout:hover {
+ background-color: #fff;
+ cursor: pointer;
+}
+
+
+
+
+.sidepanel .formfield {
+ padding: 20px 20px 0 24px;
+}
+.sidepanel .formfield .label {
+ color: #ffffff;
+ font-size: 15px;
+ line-height: 15px;
+ font-family: Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ padding: 0 0 8px 0;
+}
+.sidepanel .formfield input[type=text] {
+ border: 0;
+ padding: 10px 10px;
+ background-color: #fff;
+ color: #666;
+ line-height: 20px;
+ font-size: 16px;
+ font-family: Arial, sans-serif;
+ border-radius: 3px;
+ width: 232px;
+ -webkit-font-smoothing: antialiased;
+}
+
+.sidepanel .setupbuttons {
+ padding: 20px 0px 0 0px;
+ margin: 20px 24px 0 24px;
+ border-top: 1px solid #999;
+ border-bottom: 1px solid #999;
+ color: #999;
+}
+
+
+.sidepanel .colorchit {
+ width: 13px;
+ height: 13px;
+ border: 2px solid transparent;
+ display: block;
+ float: left;
+ border-radius: 4px;
+ margin: 4px 4px 0 0;
+}
+.sidepanel .colorchit.active,
+.sidepanel .colorchit.active:hover {
+ border-color: #fff;
+}
+.sidepanel .colorchit:hover {
+ border-color: rgba(255,255,255,.6);
+ cursor: pointer;
+}
+
+.wifisetup, .changepass {
+ width: 210px;
+ padding: 20px;
+ height: 20px;
+ text-align: center;
+ display: block;
+ background-color: rgba(255,255,255,.8);
+ border-radius: 4px;
+ color: #333;
+ z-index: 101;
+ font-size: 16px;
+ line-height: 20px;
+ font-family: Arial, sans-serif;
+ margin-bottom: 12px;
+}
+.changepass {
+ margin-bottom: 24px;
+}
+.wifisetup:hover, .changepass:hover {
+ background-color: rgba(255,255,255,1);
+ cursor: pointer;
+}
+
+#introduction {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.4);
+ z-index:4;
+}
+
+
+.intromessage {
+ width: 450px;
+ position: absolute;
+ top: 400px;
+ color: #fff;
+ left: 50%;
+ margin-left: -200px;
+ text-align: center;
+}
+
+.intromessage h1 {
+ font-size: 28px;
+ line-height: 28px;
+ font-weight: bold;
+ margin:0;
+ padding: 0 0 20px 0;
+}
+.intromessage p {
+ font-size: 21px;
+ line-height: 1.2em;
+ padding: 0 0 20px 0;
+ margin:0;
+}
+.intromessage a, .intromessage a:hover, .intromessage a:active, .intromessage a:visited {
+ color: #333;
+ text-decoration: underline;
+}
+.gotit {
+ font-size: 21px;
+ font-weight: bold;
+ line-height: 60px;
+ border-radius: 4px;
+ width: 400px;
+ margin-left:25px;
+ margin-bottom: 24px;
+ background-color: rgba(0,0,0,0.5);
+}
+.gotit:hover {
+ cursor: pointer;
+ background-color: rgba(0,0,0,1);;
+}
+#dontshow {
+ vertical-align: middle;
+ width: 20px;
+ height: 20px;
+}
+.dontshowmsg {
+ vertical-align: middle;
+}
+
+#myapps_tip {
+ position: absolute;
+ left: 440px;
+ top: 220px;
+}
+#newapp_tip {
+ position: absolute;
+ left: 140px;
+ top: 220px;
+}
+#settings_tip {
+ position: absolute;
+ right: 60px;
+ top: 20px;
+}
\ No newline at end of file
diff --git a/coder-base/static/apps/coder/js/index.js b/coder-apps/pi/coder/static/js/index.js
similarity index 99%
rename from coder-base/static/apps/coder/js/index.js
rename to coder-apps/pi/coder/static/js/index.js
index a45f043e..f2ce10e9 100644
--- a/coder-base/static/apps/coder/js/index.js
+++ b/coder-apps/pi/coder/static/js/index.js
@@ -131,7 +131,7 @@ var handleFileImport = function( ev ) {
var importfile = files[0];
//console.log( importfile );
- if (!importfile.type.match('application/zip')) {
+ if (!importfile.type.match('application/zip') && !importfile.name.match(/\.zip$/i)) {
alert('This doesn\'t appear to be a Coder project zip file');
return false;
}
diff --git a/coder-apps/pi/coder/static/media/.gitignore b/coder-apps/pi/coder/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-apps/pi/coder/static/media/myapps_tip.png b/coder-apps/pi/coder/static/media/myapps_tip.png
new file mode 100644
index 00000000..0e715b68
Binary files /dev/null and b/coder-apps/pi/coder/static/media/myapps_tip.png differ
diff --git a/coder-apps/pi/coder/static/media/newapp_tip.png b/coder-apps/pi/coder/static/media/newapp_tip.png
new file mode 100644
index 00000000..e6e44db7
Binary files /dev/null and b/coder-apps/pi/coder/static/media/newapp_tip.png differ
diff --git a/coder-apps/pi/coder/static/media/settings_tip.png b/coder-apps/pi/coder/static/media/settings_tip.png
new file mode 100644
index 00000000..6c813c33
Binary files /dev/null and b/coder-apps/pi/coder/static/media/settings_tip.png differ
diff --git a/coder-base/views/apps/coder/index.html b/coder-apps/pi/coder/views/index.html
similarity index 100%
rename from coder-base/views/apps/coder/index.html
rename to coder-apps/pi/coder/views/index.html
diff --git a/coder-base/apps/wifi/app.js b/coder-apps/pi/wifi/app/app.js
similarity index 93%
rename from coder-base/apps/wifi/app.js
rename to coder-apps/pi/wifi/app/app.js
index 80c871f6..30f4faf6 100644
--- a/coder-base/apps/wifi/app.js
+++ b/coder-apps/pi/wifi/app/app.js
@@ -90,26 +90,26 @@ exports.api_wifi_configure_handler = function( req, res ) {
var wpatemplate = "network={\n" +
- "\tssid=\"[ssid]\"\n" +
- "\tpsk=\"[password]\"\n" +
- "\tscan_ssid=1\n" +
- "\tpriority=10\n" +
- "}\n";
+ "\tssid=\"[ssid]\"\n" +
+ "\tpsk=\"[password]\"\n" +
+ "\tscan_ssid=1\n" +
+ "\tpriority=10\n" +
+ "}\n";
var weptemplate = "network={\n" +
- "\tssid=\"[ssid]\"\n" +
- "\twep_key0=\"[password]\"\n" +
- "\tscan_ssid=1\n" +
- "\tkey_mgmt=NONE\n" +
- "\tpriority=10\n" +
- "}\n";
+ "\tssid=\"[ssid]\"\n" +
+ "\twep_key0=\"[password]\"\n" +
+ "\tscan_ssid=1\n" +
+ "\tkey_mgmt=NONE\n" +
+ "\tpriority=10\n" +
+ "}\n";
var opentemplate = "network={\n" +
- "\tssid=\"[ssid]\"\n" +
- "\tscan_ssid=1\n" +
- "\tkey_mgmt=NONE\n" +
- "\tpriority=10\n" +
- "}\n";
+ "\tssid=\"[ssid]\"\n" +
+ "\tscan_ssid=1\n" +
+ "\tkey_mgmt=NONE\n" +
+ "\tpriority=10\n" +
+ "}\n";
var confentry="";
@@ -198,7 +198,7 @@ exports.api_wifi_list_handler = function( req, res ) {
var access_points = {};
var debug="";
- var addHighestSignal = function( ssid, type, signal ) {
+ var addHighestSignal = function( ssid, type, signal ) {
if ( !access_points[ssid] ) {
access_points[ssid] = { ssid: ssid, type: type, signal: signal };
} else if ( access_points[ssid].signal < signal ) {
diff --git a/coder-base/apps/wifi/meta.json b/coder-apps/pi/wifi/app/meta.json
similarity index 100%
rename from coder-base/apps/wifi/meta.json
rename to coder-apps/pi/wifi/app/meta.json
diff --git a/coder-base/static/apps/wifi/css/index.css b/coder-apps/pi/wifi/static/css/index.css
similarity index 100%
rename from coder-base/static/apps/wifi/css/index.css
rename to coder-apps/pi/wifi/static/css/index.css
diff --git a/coder-base/static/apps/wifi/js/index.js b/coder-apps/pi/wifi/static/js/index.js
similarity index 100%
rename from coder-base/static/apps/wifi/js/index.js
rename to coder-apps/pi/wifi/static/js/index.js
diff --git a/coder-apps/pi/wifi/static/media/.gitignore b/coder-apps/pi/wifi/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/views/apps/wifi/index.html b/coder-apps/pi/wifi/views/index.html
similarity index 100%
rename from coder-base/views/apps/wifi/index.html
rename to coder-apps/pi/wifi/views/index.html
diff --git a/coder-apps/tests/gpio_test/app/app.js b/coder-apps/tests/gpio_test/app/app.js
new file mode 100644
index 00000000..f5d734ad
--- /dev/null
+++ b/coder-apps/tests/gpio_test/app/app.js
@@ -0,0 +1,214 @@
+var gpio = require("gpio");
+gpio.logging = true;
+
+
+// The gpio ids we're using. Note that these aren't the pin numbers, but
+// the IDs exposed by the Pi. Search for Pi GPIO pinout for details.
+var ledGPIOID = 4; //actually pin 7, 4 down on left header
+var buttonGPIOID = 17;
+
+// Handles for our connected gpio devices
+var ledDevice;
+var buttonDevice;
+
+// A collection of all connected sockets.
+// Maps socketid => { socket: sockethandle, id: socketid }
+var connections = {};
+
+exports.settings={};
+//These are dynamically updated by the runtime
+//settings.appname - the app id (folder) where your app is installed
+//settings.viewpath - prefix to where your view html files are located
+//settings.staticurl - base url path to static assets /static/apps/appname
+//settings.appurl - base url path to this app /app/appname
+//settings.device_name - name given to this coder by the user, Ie."Billy's Coder"
+//settings.coder_owner - name of the user, Ie. "Suzie Q."
+//settings.coder_color - hex css color given to this coder.
+
+// Incoming get routes that our app knows how to respond to
+exports.get_routes = [
+ { path:'/', handler:'index_handler' }, // Render out main html page
+];
+
+// Incoming post routes that our app knows how to respond to
+// (None in this example)
+exports.post_routes = [
+];
+
+// Incoming socket events that this module will expose.
+exports.socketio_routes = [
+ { key:'connect', handler:'on_socket_connect' }, // sent by client once socket is loaded
+ { key:'setled', handler:'on_socket_setled' }, // sent by client to turn on/off the led
+];
+
+
+
+//
+// Handles sending the HTML page to the browser
+//
+exports.index_handler = function( req, res ) {
+ // Set up some template variables that are substituted in our HTML.
+ // Look in the HTML head tag to see where these are inserted.
+ var tmplvars = {};
+ tmplvars['static_url'] = exports.settings.staticurl;
+ tmplvars['app_name'] = exports.settings.appname;
+ tmplvars['app_url'] = exports.settings.appurl;
+ tmplvars['device_name'] = exports.settings.device_name;
+
+ // Send the HTML document to the web browser.
+ res.render( exports.settings.viewpath + '/index', tmplvars );
+};
+
+
+//
+// Respond to the "connect" message sent by a new socket client.
+//
+// We do two things here:
+// 1. save the socket object into the "connections" variable so we can talk to it later.
+// 2. initialize the GPIO pins if this is the first time a socket has connected.
+//
+exports.on_socket_connect = function( socket, data ) {
+ console.log( 'socket connect from ID: ' + socket.socketID );
+ console.log( data );
+
+ // Enable the GPIO pins if this is the first connection
+ if ( Object.keys( connections ).length <= 0 ) {
+ enableGPIO();
+ }
+
+ // Store information about this socket so we can communicate with
+ // all connected sockets in the future.
+ connections[socket.socketID] = {
+ socket: socket,
+ id: socket.socketID
+ };
+
+ // Watch for this socket to disconnect so that we can remove it from
+ // our collection of connected sockets.
+ socket.on('disconnect', function() {
+ console.log( 'socket disconnect from ID: ' + socket.socketID );
+ delete connections[socket.socketID];
+
+ //Free up the GPIO when the last socket disconnects
+ if ( Object.keys( connections ).length <= 0 ) {
+ disableGPIO();
+ connected = false;
+ }
+ });
+
+};
+
+//
+// Respond to a "setled" message from a socket connection to update the LED value
+//
+exports.on_socket_setled = function( socket, data ) {
+ if ( data.value !== "undefined" ) {
+ setLED( data.value );
+ }
+};
+
+
+
+//
+// This is called once from our first socket connection.
+// - set up the LED GPIO as an output
+// - set up the Button GPIO as an input and tie it to send a "change" message
+//
+var enableGPIO = function() {
+
+ // Set up the LED output GPIO
+ console.log("Setting up LED as an output on GPIO " + ledGPIOID );
+ ledDevice = gpio.export( ledGPIOID, {
+ ready: function() {
+ // This works around a bug in gpio, where sometimes this device
+ // doesn't become immediately available.
+ setTimeout( function() {
+ ledDevice.setDirection("out");
+ }, 100); //wait 100 ms before setting direction
+ }
+ });
+
+ // Set up the button input GPIO
+ console.log("Setting up Button as an input on GPIO" + buttonGPIOID);
+ buttonDevice = gpio.export( buttonGPIOID, {
+ direction: "in",
+ ready: function() {
+
+ // Set up buttonDevice to call the buttonChange
+ // function (below) whenever its value changes.
+ buttonDevice.on("change", buttonChange);
+ }
+ });
+
+};
+
+//
+// This is called when the last socket disconnects.
+// It releases our GPIO pins so they can be used by another program.
+//
+var disableGPIO = function() {
+ console.log("Disabling GPIO" + ledGPIOID );
+ ledDevice.removeAllListeners();
+ ledDevice.reset();
+ ledDevice.unexport();
+
+ console.log("Disabling GPIO" + buttonGPIOID );
+ buttonDevice.removeAllListeners();
+ buttonDevice.reset();
+ buttonDevice.unexport();
+};
+
+//
+// This is triggered by the GPIO "change" event on buttonDevice. This was
+// set up inside emabledGPIO().
+//
+// The change event sends this function a value, either 0 (off) or 1 (on).
+//
+var buttonChange = function( val ) {
+ // Recall that this code is running on the device. We need to send a
+ // socket message with the button data to our javascript in the
+ // web browser. In fact, we need to send this data to every connected
+ // socket, since there may be more than one browser window looking at
+ // this page.
+
+ console.log( "buttonChange event with value: " + val );
+
+ // Iterate through all of our socket connections
+ for ( var socketid in connections ) {
+ // Get the socket object for this socket
+ var socket = connections[socketid].socket;
+
+ // The "appdata" event will be received by the Coder.socketConnection
+ // object in the front end code and sent to the appropriate listener
+ // that we've defined.
+ // The "buttonupdate" key refers to a listener we set up on the front
+ // end with the code:
+ // Coder.socketConnection.addListener( "buttonupdate", function... )
+ socket.emit( "appdata", {
+ key: "buttonupdate",
+ data: val
+ });
+ }
+};
+
+//
+// Set the value on the LED GPIO device, either 0 (off) or 1 (on).
+//
+var setLED = function( val ) {
+ val = parseInt( val );
+ if ( val != 0 ) {
+ val = 1;
+ }
+ ledDevice.set( val );
+};
+
+//
+// Called by Coder whenever this module is reloaded. This usually happens when
+// you save your code in the editor. This is a good place to destroy any intervals
+// or clean up any long running code or handles.
+//
+exports.on_destroy = function() {
+};
+
+
+
diff --git a/coder-apps/tests/gpio_test/app/meta.json b/coder-apps/tests/gpio_test/app/meta.json
new file mode 100644
index 00000000..ba5193b7
--- /dev/null
+++ b/coder-apps/tests/gpio_test/app/meta.json
@@ -0,0 +1,8 @@
+{
+ "created": "2013-11-30",
+ "modified": "2014-01-14",
+ "color": "#2ecc71",
+ "author": "",
+ "name": "GPIO Test",
+ "hidden": false
+}
\ No newline at end of file
diff --git a/coder-apps/tests/gpio_test/static/css/index.css b/coder-apps/tests/gpio_test/static/css/index.css
new file mode 100644
index 00000000..f589f589
--- /dev/null
+++ b/coder-apps/tests/gpio_test/static/css/index.css
@@ -0,0 +1,61 @@
+
+.pagecontent {
+ padding: 24px;
+ min-width: 880px;
+}
+
+.buttons {
+ float: left;
+ width: 240px;
+}
+.diagram {
+ width: 640px;
+ float: left;
+ padding-bottom: 100px;
+}
+.clear {
+ clear: both;
+}
+
+#buttonval {
+ width: 200px;
+ height: 150px;
+ margin: 24px 0;
+ color: #fff;
+ background-color: #000000;
+ text-align: center;
+ font-weight: bold;
+ line-height: 150px;
+}
+#buttonval.on {
+ background-color: #F03050;
+}
+
+
+.button {
+ cursor: pointer;
+ width: 200px;
+ height: 150px;
+ margin: 24px 0;
+ background-color: #3498D8;
+ line-height: 150px;
+ text-align: center;
+ color: #fff;
+ font-weight: bold;
+}
+
+.button.press {
+ background-color: #2488A8;
+}
+
+
+#output {
+ position: fixed;
+ left:0;
+ right:0;
+ bottom:0;
+ height: 100px;
+ color: #fff;
+ background-color: #000;
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/coder-apps/tests/gpio_test/static/js/index.js b/coder-apps/tests/gpio_test/static/js/index.js
new file mode 100644
index 00000000..6c14c96b
--- /dev/null
+++ b/coder-apps/tests/gpio_test/static/js/index.js
@@ -0,0 +1,144 @@
+///////////////////////
+// GPIO Test
+// Sample code to interact with Raspberry Pi hardware, blink an LED,
+// and detect a physical button press.
+//
+// This part of the code runs in your web browser. It's responsible
+// for handling user input from the web browser, sending commands
+// to the Raspberry Pi, and listening for updates that the device
+// sends back.
+//
+// The code here communicates with another program that runs directly
+// on the Pi (not in your browser). That device-side code can be found
+// in the Node tab.
+////////////////////////
+
+$(document).ready( function() {
+
+ // Connection can take a second. Let the user know what's happening.
+ addOutputMessage( "Connecting... see the debug console for log messages." );
+
+ // This establishes a socket connection to the Coder device. A
+ // socket conection stays open while the user is viewing this page,
+ // which allows us to send a receive data very quickly from the device
+ // instead of checking for updates multiple times a second.
+ //
+ // Coder.socketConnection.init takes a callback function that will
+ // be executed once the connection is established. Anything that
+ // requires an established connection in order to function
+ // correctly should be placed in here.
+ Coder.socketConnection.init(function(){
+
+ // Each connection gets a unique ID.
+ addOutputMessage( "Connected with ID: " + Coder.socketConnection.socketID );
+
+ // Send a "connect" message to our Node page when we first connect.
+ Coder.socketConnection.sendData( 'connect', {} );
+
+
+ // Listen for a "buttonupdate" socket message from the device
+ Coder.socketConnection.addListener( 'buttonupdate', function( d ){
+ console.log("button gpio value: " + d);
+
+ // The data we get should be an integer, 0 (off) or 1 (button pressed).
+ var val = parseInt( d );
+ if ( d === 1 ) {
+ $("#buttonval").addClass('on');
+ } else {
+ $("#buttonval").removeClass('on');
+ }
+ });
+
+
+ // Set an interval that will repeatedly send LED on and
+ // LED off messages to the device. This will cause the led
+ // to blink.
+ blinkEnabled = false; // it's off by default
+ setInterval( function() {
+ // Only blink the LED if the user has
+ // turned this feature on.
+ if ( blinkEnabled ) {
+ ledToggle(); // toggles the led. see below.
+ }
+ }, 100 ); // This function is repeatedly called every 100ms
+
+
+
+ $("#blinkon").click( function() {
+ startBlink();
+ ledOn(); // start out with the light in the on state.
+ });
+ $("#blinkoff").click( function() {
+ stopBlink();
+ ledOff(); // also make sure the light is off
+ });
+ $("#toggle").click( function() {
+ stopBlink(); // discontinue blinking in case it was running
+ ledToggle(); // flip the LED state
+ });
+
+ // Treat this like a push button that turns the light off while
+ // the mouse is pressed, and turns it off immediately on release.
+ $("#push").on("mousedown", function() {
+ stopBlink();
+ ledOn();
+ }).on("mouseup", function() {
+ stopBlink();
+ ledOff();
+ });
+
+
+ });
+
+});
+
+// The "ledValue" variable lets us keep track of the LED's
+// current state. 1 is on, 0 is off.
+var ledValue = 0;
+
+// Send a "setled" message to the device with a value of 1 (on)
+var ledOn = function() {
+ ledValue = 1;
+ Coder.socketConnection.sendData( 'setled', {
+ 'value': ledValue
+ });
+};
+
+// Send a "setled" message to the device with a value of 0 (on)
+var ledOff = function() {
+ ledValue = 0;
+ Coder.socketConnection.sendData( 'setled', {
+ 'value': ledValue
+ });
+};
+
+// Switch the led state.
+// If it's on, turn it off. If it's off, turn it on.
+var ledToggle = function() {
+ if ( ledValue === 1 ) {
+ ledOff();
+ } else {
+ ledOn();
+ }
+};
+
+var blinkEnabled = false;
+var startBlink = function() {
+ blinkEnabled = true; // enables blinking in our interval timer (above)
+ $("#blinkon").hide();
+ $("#blinkoff").show();
+};
+var stopBlink = function() {
+ blinkEnabled = false; // enables blinking in our interval timer (above)
+ $("#blinkon").show();
+ $("#blinkoff").hide();
+};
+
+
+// Append a new P tag to the #output DIV
+var addOutputMessage = function( text ) {
+ var $output = $("#output");
+ $output.append( $("").text( text ) );
+ console.log( text );
+};
+
diff --git a/coder-apps/tests/gpio_test/static/media/.gitignore b/coder-apps/tests/gpio_test/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-apps/tests/gpio_test/static/media/wiring_diagram.jpg b/coder-apps/tests/gpio_test/static/media/wiring_diagram.jpg
new file mode 100644
index 00000000..1ad680ed
Binary files /dev/null and b/coder-apps/tests/gpio_test/static/media/wiring_diagram.jpg differ
diff --git a/coder-apps/tests/gpio_test/views/index.html b/coder-apps/tests/gpio_test/views/index.html
new file mode 100644
index 00000000..82ab5343
--- /dev/null
+++ b/coder-apps/tests/gpio_test/views/index.html
@@ -0,0 +1,45 @@
+
+
+
+ Coder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
GPIO Test
+
+
BLINK
+
STOP BLINK
+
TOGGLE
+
PUSH
+
HARDWARE PRESS
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coder-apps/tests/relay_test/app/app.js b/coder-apps/tests/relay_test/app/app.js
new file mode 100644
index 00000000..2bb4ed84
--- /dev/null
+++ b/coder-apps/tests/relay_test/app/app.js
@@ -0,0 +1,249 @@
+var gpio = require("gpio");
+gpio.logging = true;
+
+
+// The gpio ids we're using. Note that these aren't the pin numbers, but
+// the IDs exposed by the Pi. Search for Pi GPIO pinout for details.
+var ledGPIOID = 4; //actually pin 7, 4 down on left header
+var buttonGPIOID = 17;
+var relayGPIOID = 18;
+
+// Handles for our connected gpio devices
+var ledDevice;
+var buttonDevice;
+var relayDevice;
+
+// A collection of all connected sockets.
+// Maps socketid => { socket: sockethandle, id: socketid }
+var connections = {};
+
+exports.settings={};
+//These are dynamically updated by the runtime
+//settings.appname - the app id (folder) where your app is installed
+//settings.viewpath - prefix to where your view html files are located
+//settings.staticurl - base url path to static assets /static/apps/appname
+//settings.appurl - base url path to this app /app/appname
+//settings.device_name - name given to this coder by the user, Ie."Billy's Coder"
+//settings.coder_owner - name of the user, Ie. "Suzie Q."
+//settings.coder_color - hex css color given to this coder.
+
+// Incoming get routes that our app knows how to respond to
+exports.get_routes = [
+ { path:'/', handler:'index_handler' }, // Render out main html page
+];
+
+// Incoming post routes that our app knows how to respond to
+// (None in this example)
+exports.post_routes = [
+];
+
+// Incoming socket events that this module will expose.
+exports.socketio_routes = [
+ { key:'connect', handler:'on_socket_connect' }, // sent by client once socket is loaded
+ { key:'setled', handler:'on_socket_setled' }, // sent by client to turn on/off the led
+ { key:'setrelay', handler:'on_socket_setrelay' }, // sent by client to turn on/off the relay
+];
+
+
+
+//
+// Handles sending the HTML page to the browser
+//
+exports.index_handler = function( req, res ) {
+ // Set up some template variables that are substituted in our HTML.
+ // Look in the HTML head tag to see where these are inserted.
+ var tmplvars = {};
+ tmplvars['static_url'] = exports.settings.staticurl;
+ tmplvars['app_name'] = exports.settings.appname;
+ tmplvars['app_url'] = exports.settings.appurl;
+ tmplvars['device_name'] = exports.settings.device_name;
+
+ // Send the HTML document to the web browser.
+ res.render( exports.settings.viewpath + '/index', tmplvars );
+};
+
+
+//
+// Respond to the "connect" message sent by a new socket client.
+//
+// We do two things here:
+// 1. save the socket object into the "connections" variable so we can talk to it later.
+// 2. initialize the GPIO pins if this is the first time a socket has connected.
+//
+exports.on_socket_connect = function( socket, data ) {
+ console.log( 'socket connect from ID: ' + socket.socketID );
+ console.log( data );
+
+ // Enable the GPIO pins if this is the first connection
+ if ( Object.keys( connections ).length <= 0 ) {
+ enableGPIO();
+ }
+
+ // Store information about this socket so we can communicate with
+ // all connected sockets in the future.
+ connections[socket.socketID] = {
+ socket: socket,
+ id: socket.socketID
+ };
+
+ // Watch for this socket to disconnect so that we can remove it from
+ // our collection of connected sockets.
+ socket.on('disconnect', function() {
+ console.log( 'socket disconnect from ID: ' + socket.socketID );
+ delete connections[socket.socketID];
+
+ //Free up the GPIO when the last socket disconnects
+ if ( Object.keys( connections ).length <= 0 ) {
+ disableGPIO();
+ connected = false;
+ }
+ });
+
+};
+
+//
+// Respond to a "setled" message from a socket connection to update the LED value
+//
+exports.on_socket_setled = function( socket, data ) {
+ if ( data.value !== "undefined" ) {
+ setLED( data.value );
+ }
+};
+
+//
+// Respond to a "setrelay" message from a socket connection to update the relay value
+//
+exports.on_socket_setrelay = function( socket, data ) {
+ if ( data.value !== "undefined" ) {
+ setRelay( data.value );
+ }
+};
+
+//
+// This is called once from our first socket connection.
+// - set up the LED GPIO as an output
+// - set up the Button GPIO as an input and tie it to send a "change" message
+//
+var enableGPIO = function() {
+
+ // Set up the LED output GPIO
+ console.log("Setting up LED as an output on GPIO " + ledGPIOID );
+ ledDevice = gpio.export( ledGPIOID, {
+ ready: function() {
+ // This works around a bug in gpio, where sometimes this device
+ // doesn't become immediately available.
+ setTimeout( function() {
+ ledDevice.setDirection("out");
+ }, 100); //wait 100 ms before setting direction
+ }
+ });
+
+ console.log("Setting up Relay as an output on GPIO " + relayGPIOID );
+ relayDevice = gpio.export( relayGPIOID, {
+ direction: "out",
+ ready: function() {
+ // This works around a bug in gpio, where sometimes this device
+ // doesn't become immediately available.
+ setTimeout( function() {
+ relayDevice.setDirection("out");
+ }, 400); //wait 100 ms before setting direction
+ }
+ });
+
+ // Set up the button input GPIO
+ console.log("Setting up Button as an input on GPIO" + buttonGPIOID);
+ buttonDevice = gpio.export( buttonGPIOID, {
+ direction: "in",
+ ready: function() {
+
+ // Set up buttonDevice to call the buttonChange
+ // function (below) whenever its value changes.
+ buttonDevice.on("change", buttonChange);
+ }
+ });
+
+};
+
+//
+// This is called when the last socket disconnects.
+// It releases our GPIO pins so they can be used by another program.
+//
+var disableGPIO = function() {
+ console.log("Disabling GPIO" + ledGPIOID );
+ ledDevice.removeAllListeners();
+ ledDevice.reset();
+ ledDevice.unexport();
+
+ console.log("Disabling GPIO" + relayGPIOID );
+ relayDevice.removeAllListeners();
+ relayDevice.reset();
+ relayDevice.unexport();
+
+ console.log("Disabling GPIO" + buttonGPIOID );
+ buttonDevice.removeAllListeners();
+ buttonDevice.reset();
+ buttonDevice.unexport();
+};
+
+//
+// This is triggered by the GPIO "change" event on buttonDevice. This was
+// set up inside emabledGPIO().
+//
+// The change event sends this function a value, either 0 (off) or 1 (on).
+//
+var buttonChange = function( val ) {
+ // Recall that this code is running on the device. We need to send a
+ // socket message with the button data to our javascript in the
+ // web browser. In fact, we need to send this data to every connected
+ // socket, since there may be more than one browser window looking at
+ // this page.
+
+ console.log( "buttonChange event with value: " + val );
+
+ // Iterate through all of our socket connections
+ for ( var socketid in connections ) {
+ // Get the socket object for this socket
+ var socket = connections[socketid].socket;
+
+ // The "appdata" event will be received by the Coder.socketConnection
+ // object in the front end code and sent to the appropriate listener
+ // that we've defined.
+ // The "buttonupdate" key refers to a listener we set up on the front
+ // end with the code:
+ // Coder.socketConnection.addListener( "buttonupdate", function... )
+ socket.emit( "appdata", {
+ key: "buttonupdate",
+ data: val
+ });
+ }
+};
+
+//
+// Set the value on the LED GPIO device, either 0 (off) or 1 (on).
+//
+var setLED = function( val ) {
+ val = parseInt( val );
+ if ( val != 0 ) {
+ val = 1;
+ }
+ ledDevice.set( val );
+};
+
+var setRelay = function( val ) {
+ val = parseInt( val );
+ if ( val != 0 ) {
+ val = 1;
+ }
+ relayDevice.set( val );
+};
+
+//
+// Called by Coder whenever this module is reloaded. This usually happens when
+// you save your code in the editor. This is a good place to destroy any intervals
+// or clean up any long running code or handles.
+//
+exports.on_destroy = function() {
+};
+
+
+
diff --git a/coder-apps/tests/relay_test/app/meta.json b/coder-apps/tests/relay_test/app/meta.json
new file mode 100644
index 00000000..6f34598f
--- /dev/null
+++ b/coder-apps/tests/relay_test/app/meta.json
@@ -0,0 +1,8 @@
+{
+ "created": "2013-11-30",
+ "modified": "2014-01-14",
+ "color": "#2ecc71",
+ "author": "",
+ "name": "Relay Test",
+ "hidden": false
+}
\ No newline at end of file
diff --git a/coder-apps/tests/relay_test/static/css/index.css b/coder-apps/tests/relay_test/static/css/index.css
new file mode 100644
index 00000000..52c0643d
--- /dev/null
+++ b/coder-apps/tests/relay_test/static/css/index.css
@@ -0,0 +1,63 @@
+
+.pagecontent {
+ padding: 24px;
+ min-width: 880px;
+}
+
+.buttons {
+ /* float: left;
+ width: 240px;
+ */
+}
+.diagram {
+ width: 640px;
+ float: left;
+ padding-bottom: 100px;
+}
+.clear {
+ clear: both;
+}
+
+#buttonval {
+ width: 200px;
+ height: 150px;
+ margin: 24px 0;
+ color: #fff;
+ background-color: #000000;
+ text-align: center;
+ font-weight: bold;
+ line-height: 150px;
+}
+#buttonval.on {
+ background-color: #F03050;
+}
+
+
+.button {
+ cursor: pointer;
+ width: 200px;
+ height: 150px;
+ margin: 24px 0;
+ background-color: #3498D8;
+ line-height: 150px;
+ text-align: center;
+ color: #fff;
+ font-weight: bold;
+ display: inline-block;
+}
+
+.button.press {
+ background-color: #2488A8;
+}
+
+
+#output {
+ position: fixed;
+ left:0;
+ right:0;
+ bottom:0;
+ height: 100px;
+ color: #fff;
+ background-color: #000;
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/coder-apps/tests/relay_test/static/js/index.js b/coder-apps/tests/relay_test/static/js/index.js
new file mode 100644
index 00000000..7837a544
--- /dev/null
+++ b/coder-apps/tests/relay_test/static/js/index.js
@@ -0,0 +1,160 @@
+///////////////////////
+// GPIO Test
+// Sample code to interact with Raspberry Pi hardware, blink an LED,
+// and detect a physical button press.
+//
+// This part of the code runs in your web browser. It's responsible
+// for handling user input from the web browser, sending commands
+// to the Raspberry Pi, and listening for updates that the device
+// sends back.
+//
+// The code here communicates with another program that runs directly
+// on the Pi (not in your browser). That device-side code can be found
+// in the Node tab.
+////////////////////////
+
+$(document).ready( function() {
+
+ // Connection can take a second. Let the user know what's happening.
+ addOutputMessage( "Connecting... see the debug console for log messages." );
+
+ // This establishes a socket connection to the Coder device. A
+ // socket conection stays open while the user is viewing this page,
+ // which allows us to send a receive data very quickly from the device
+ // instead of checking for updates multiple times a second.
+ //
+ // Coder.socketConnection.init takes a callback function that will
+ // be executed once the connection is established. Anything that
+ // requires an established connection in order to function
+ // correctly should be placed in here.
+ Coder.socketConnection.init(function(){
+
+ // Each connection gets a unique ID.
+ addOutputMessage( "Connected with ID: " + Coder.socketConnection.socketID );
+
+ // Send a "connect" message to our Node page when we first connect.
+ Coder.socketConnection.sendData( 'connect', {} );
+
+
+ // Listen for a "buttonupdate" socket message from the device
+ Coder.socketConnection.addListener( 'buttonupdate', function( d ){
+ console.log("button gpio value: " + d);
+
+ // The data we get should be an integer, 0 (off) or 1 (button pressed).
+ var val = parseInt( d );
+ if ( d === 1 ) {
+ $("#buttonval").addClass('on');
+ } else {
+ $("#buttonval").removeClass('on');
+ }
+ });
+
+
+ // Set an interval that will repeatedly send LED on and
+ // LED off messages to the device. This will cause the led
+ // to blink.
+ blinkEnabled = false; // it's off by default
+ setInterval( function() {
+ // Only blink the LED if the user has
+ // turned this feature on.
+ if ( blinkEnabled ) {
+ ledToggle(); // toggles the led. see below.
+ }
+ }, 100 ); // This function is repeatedly called every 100ms
+
+
+
+ $("#blinkon").click( function() {
+ startBlink();
+ ledOn(); // start out with the light in the on state.
+ });
+ $("#blinkoff").click( function() {
+ stopBlink();
+ ledOff(); // also make sure the light is off
+ });
+ $("#toggle").click( function() {
+ stopBlink(); // discontinue blinking in case it was running
+ ledToggle(); // flip the LED state
+ });
+
+ // Treat this like a push button that turns the light off while
+ // the mouse is pressed, and turns it off immediately on release.
+ $("#push").on("mousedown", function() {
+ stopBlink();
+ ledOn();
+ }).on("mouseup", function() {
+ stopBlink();
+ ledOff();
+ });
+
+ $("#push2").on("mousedown", function() {
+ relayOn();
+ }).on("mouseup", function() {
+ relayOff();
+ });
+ });
+
+});
+
+
+var relayOn = function() {
+ Coder.socketConnection.sendData( 'setrelay', {
+ 'value': 1
+ });
+};
+var relayOff = function() {
+ Coder.socketConnection.sendData( 'setrelay', {
+ 'value': 0
+ });
+};
+
+// The "ledValue" variable lets us keep track of the LED's
+// current state. 1 is on, 0 is off.
+var ledValue = 0;
+
+// Send a "setled" message to the device with a value of 1 (on)
+var ledOn = function() {
+ ledValue = 1;
+ Coder.socketConnection.sendData( 'setled', {
+ 'value': ledValue
+ });
+};
+
+// Send a "setled" message to the device with a value of 0 (on)
+var ledOff = function() {
+ ledValue = 0;
+ Coder.socketConnection.sendData( 'setled', {
+ 'value': ledValue
+ });
+};
+
+// Switch the led state.
+// If it's on, turn it off. If it's off, turn it on.
+var ledToggle = function() {
+ if ( ledValue === 1 ) {
+ ledOff();
+ } else {
+ ledOn();
+ }
+};
+
+var blinkEnabled = false;
+var startBlink = function() {
+ blinkEnabled = true; // enables blinking in our interval timer (above)
+ $("#blinkon").hide();
+ $("#blinkoff").show();
+};
+var stopBlink = function() {
+ blinkEnabled = false; // enables blinking in our interval timer (above)
+ $("#blinkon").show();
+ $("#blinkoff").hide();
+};
+
+
+// Append a new P tag to the #output DIV
+var addOutputMessage = function( text ) {
+ var $output = $("#output");
+ $output.append( $("").text( text ) );
+ console.log( text );
+};
+
diff --git a/coder-apps/tests/relay_test/static/media/.gitignore b/coder-apps/tests/relay_test/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-apps/tests/relay_test/views/index.html b/coder-apps/tests/relay_test/views/index.html
new file mode 100644
index 00000000..e4efedbd
--- /dev/null
+++ b/coder-apps/tests/relay_test/views/index.html
@@ -0,0 +1,46 @@
+
+
+
+ Coder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
GPIO Test
+
+
BLINK
+
STOP BLINK
+
TOGGLE
+
PUSH
+
PUSH 2
+
HARDWARE PRESS
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coder-apps/tests/socket_test/app/app.js b/coder-apps/tests/socket_test/app/app.js
new file mode 100644
index 00000000..cdb4ffbe
--- /dev/null
+++ b/coder-apps/tests/socket_test/app/app.js
@@ -0,0 +1,83 @@
+exports.settings={};
+//These are dynamically updated by the runtime
+//settings.appname - the app id (folder) where your app is installed
+//settings.viewpath - prefix to where your view html files are located
+//settings.staticurl - base url path to static assets /static/apps/appname
+//settings.appurl - base url path to this app /app/appname
+//settings.device_name - name given to this coder by the user, Ie."Billy's Coder"
+//settings.coder_owner - name of the user, Ie. "Suzie Q."
+//settings.coder_color - hex css color given to this coder.
+
+exports.get_routes = [
+ { path:'/', handler:'index_handler' },
+];
+
+exports.post_routes = [
+];
+
+exports.socketio_routes = [
+ { key:'connect', handler:'on_socket_connect' },
+ { key:'message', handler:'on_socket_message' }
+];
+
+
+var connections = {};
+
+exports.index_handler = function( req, res ) {
+ var tmplvars = {};
+ tmplvars['static_url'] = exports.settings.staticurl;
+ tmplvars['app_name'] = exports.settings.appname;
+ tmplvars['app_url'] = exports.settings.appurl;
+ tmplvars['device_name'] = exports.settings.device_name;
+
+ res.render( exports.settings.viewpath + '/index', tmplvars );
+};
+
+exports.on_socket_connect = function( socket, data ) {
+ console.log( 'socket connect from ID: ' + socket.socketID );
+ console.log( data );
+
+ connections[socket.socketID] = {
+ socket: socket,
+ name: data.name,
+ id: socket.socketID
+ };
+ socket.on('disconnect', function() {
+ console.log( 'socket disconnect from ID: ' + socket.socketID );
+ delete connections[socket.socketID];
+ });
+
+};
+
+exports.on_socket_message = function( socket, data ) {
+ console.log( 'socket message from: ' );
+
+ console.log( socket.handshake.sessionID );
+ var me = connections[data.id];
+ if ( me ) {
+ var updated = {};
+ for ( var k in connections ) {
+ console.log( connections[k].name );
+
+ if ( !connections[k].socket.disconnected ) {
+ updated[k] = connections[k];
+ if ( connections[k] !== me ) {
+ var s = connections[k].socket;
+ s.emit('appdata', {
+ key: 'message',
+ data: {
+ name: me.name,
+ message: data.message
+ }
+ });
+ }
+ }
+ }
+ connections = updated;
+ }
+
+};
+
+
+exports.on_destroy = function() {
+};
\ No newline at end of file
diff --git a/coder-apps/tests/socket_test/app/meta.json b/coder-apps/tests/socket_test/app/meta.json
new file mode 100644
index 00000000..022d7754
--- /dev/null
+++ b/coder-apps/tests/socket_test/app/meta.json
@@ -0,0 +1,8 @@
+{
+ "created": "2013-11-30",
+ "modified": "2013-12-03",
+ "color": "#2ecc71",
+ "author": "",
+ "name": "Socket Test",
+ "hidden": false
+}
\ No newline at end of file
diff --git a/coder-apps/tests/socket_test/static/css/index.css b/coder-apps/tests/socket_test/static/css/index.css
new file mode 100644
index 00000000..408ef623
--- /dev/null
+++ b/coder-apps/tests/socket_test/static/css/index.css
@@ -0,0 +1,4 @@
+
+.pagecontent {
+ padding: 24px;
+}
\ No newline at end of file
diff --git a/coder-apps/tests/socket_test/static/js/index.js b/coder-apps/tests/socket_test/static/js/index.js
new file mode 100644
index 00000000..34cbb575
--- /dev/null
+++ b/coder-apps/tests/socket_test/static/js/index.js
@@ -0,0 +1,26 @@
+
+$(document).ready( function() {
+
+ //This code will run after your page loads
+ Coder.socketConnection.init(function(){
+
+ addOutputMessage( "Connected with ID: " + Coder.socketConnection.socketID );
+
+
+ Coder.socketConnection.sendData( 'connect', {'name':'testing'} );
+
+ Coder.socketConnection.addListener( 'message', function( d ){
+ console.log("message from: " + d.name + " : " + d.message);
+ });
+ });
+
+ addOutputMessage( "Connecting... see the debug console for log messages." );
+
+});
+
+var addOutputMessage = function( text ) {
+ var $output = $("#output");
+ $output.prepend( $("").text( text ) );
+ console.log( text );
+}
+
diff --git a/coder-apps/tests/socket_test/static/media/.gitignore b/coder-apps/tests/socket_test/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-apps/tests/socket_test/views/index.html b/coder-apps/tests/socket_test/views/index.html
new file mode 100644
index 00000000..7fab1e9e
--- /dev/null
+++ b/coder-apps/tests/socket_test/views/index.html
@@ -0,0 +1,34 @@
+
+
+
+ Coder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Socket Test
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coder-apps/tests/spi_test/app/app.js b/coder-apps/tests/spi_test/app/app.js
new file mode 100644
index 00000000..8da16d0b
--- /dev/null
+++ b/coder-apps/tests/spi_test/app/app.js
@@ -0,0 +1,202 @@
+var SPI = require('pi-spi');
+
+
+// The SPI linux device. Either /dev/spidev0.0 or /dev/spidev0.1
+var spidev = '/dev/spidev0.0';
+
+// The handle to the device driver
+var spiDevice = null;
+
+
+
+// A collection of all connected sockets.
+// Maps socketid => { socket: sockethandle, id: socketid }
+var connections = {};
+
+exports.settings={};
+//These are dynamically updated by the runtime
+//settings.appname - the app id (folder) where your app is installed
+//settings.viewpath - prefix to where your view html files are located
+//settings.staticurl - base url path to static assets /static/apps/appname
+//settings.appurl - base url path to this app /app/appname
+//settings.device_name - name given to this coder by the user, Ie."Billy's Coder"
+//settings.coder_owner - name of the user, Ie. "Suzie Q."
+//settings.coder_color - hex css color given to this coder.
+
+// Incoming get routes that our app knows how to respond to
+exports.get_routes = [
+ { path:'/', handler:'index_handler' }, // Render out main html page
+];
+
+// Incoming post routes that our app knows how to respond to
+// (None in this example)
+exports.post_routes = [
+];
+
+// Incoming socket events that this module will expose.
+exports.socketio_routes = [
+ { key:'connect', handler:'on_socket_connect' }, // sent by client once socket is loaded
+];
+
+
+
+//
+// Handles sending the HTML page to the browser
+//
+exports.index_handler = function( req, res ) {
+ // Set up some template variables that are substituted in our HTML.
+ // Look in the HTML head tag to see where these are inserted.
+ var tmplvars = {};
+ tmplvars['static_url'] = exports.settings.staticurl;
+ tmplvars['app_name'] = exports.settings.appname;
+ tmplvars['app_url'] = exports.settings.appurl;
+ tmplvars['device_name'] = exports.settings.device_name;
+
+ // Send the HTML document to the web browser.
+ res.render( exports.settings.viewpath + '/index', tmplvars );
+};
+
+
+//
+// Respond to the "connect" message sent by a new socket client.
+//
+// We do two things here:
+// 1. save the socket object into the "connections" variable so we can talk to it later.
+// 2. initialize the SPI device if this is the first time a socket has connected.
+//
+exports.on_socket_connect = function( socket, data ) {
+ console.log( 'socket connect from ID: ' + socket.socketID );
+ console.log( data );
+
+ // Enable the SPI device if this is the first connection
+ if ( Object.keys( connections ).length <= 0 ) {
+ enableSPI();
+ }
+
+ // Store information about this socket so we can communicate with
+ // all connected sockets in the future.
+ connections[socket.socketID] = {
+ socket: socket,
+ id: socket.socketID
+ };
+
+ // Watch for this socket to disconnect so that we can remove it from
+ // our collection of connected sockets.
+ socket.on('disconnect', function() {
+ console.log( 'socket disconnect from ID: ' + socket.socketID );
+ delete connections[socket.socketID];
+
+ //Free up the GPIO when the last socket disconnects
+ if ( Object.keys( connections ).length <= 0 ) {
+ disableSPI();
+ connected = false;
+ spiDevice = null;
+ }
+ });
+
+};
+
+
+
+
+
+//
+// This is called once from our first socket connection.
+// - set up the SPI device
+// - read input from the device and send spiupdate events
+//
+var updateInterval;
+var enableSPI = function() {
+
+ // Set up the SPI device
+ console.log("Setting up SPI on " + spidev );
+ spiDevice = SPI.initialize( spidev );
+
+
+ //Poll the device for updates once a second.
+ updateInterval = setInterval( readADCData, 1000 );
+
+
+};
+
+//
+// This is called when the last socket disconnects.
+// It releases our GPIO pins so they can be used by another program.
+//
+var disableSPI = function() {
+
+ console.log("Disabling SPI device" + spidev );
+
+ clearInterval( updateInterval );
+ spiDevice.close();
+
+};
+
+var toBin = function( d ) {
+ return ("00000000" + d.toString(2)).substr( -8 );
+};
+
+//
+// Reads channel 0 analog value from an MCP3008 ADC chip
+//
+var readADCData = function() {
+ // Recall that this code is running on the device. We need to send a
+ // socket message with the data to our javascript in the
+ // web browser. In fact, we need to send this data to every connected
+ // socket, since there may be more than one browser window looking at
+ // this page.
+
+ // The message used to tell an MCP3008 ADC chip to return the
+ // analog reading of channel 0;
+ var channel = 0;
+ var message = new Buffer([1, (8+channel)<<4, 0]);
+
+ spiDevice.transfer( message, message.length, function( error, data ) {
+ if ( error ) {
+ console.log( "read error " + error );
+ } else {
+
+ //last 2 bits of 2nd byte, shifted left 8 bits, added to all 8 bits of third byte = 10 data bits.
+ var val = ((data[1]&3) << 8) + data[2];
+ //val = data[2];
+ console.log( "raw data: " + toBin( data[0] ) + " " + toBin( data[1] ) + " " + toBin( data[2] ) );
+ //console.log( "value: " + val );
+
+ // Iterate through all of our socket connections
+ for ( var socketid in connections ) {
+ // Get the socket object for this socket
+ var socket = connections[socketid].socket;
+
+ // The "appdata" event will be received by the Coder.socketConnection
+ // object in the front end code and sent to the appropriate listener
+ // that we've defined.
+ // The "analogdata" key refers to a listener we set up on the front
+ // end with the code:
+ // Coder.socketConnection.addListener( "analogdata", function... )
+ socket.emit( "appdata", {
+ key: "analogdata",
+ data: val
+ });
+ }
+
+
+ }
+ });
+
+};
+
+
+//
+// Called by Coder whenever this module is reloaded. This usually happens when
+// you save your code in the editor. This is a good place to destroy any intervals
+// or clean up any long running code or handles.
+//
+exports.on_destroy = function() {
+ if ( spiDevice !== null ) {
+ disableSPI();
+ }
+
+};
+
+
+
diff --git a/coder-apps/tests/spi_test/app/meta.json b/coder-apps/tests/spi_test/app/meta.json
new file mode 100644
index 00000000..ab803eca
--- /dev/null
+++ b/coder-apps/tests/spi_test/app/meta.json
@@ -0,0 +1,8 @@
+{
+ "created": "2013-11-30",
+ "modified": "2014-04-02",
+ "color": "#2ecc71",
+ "author": "",
+ "name": "SPI Test",
+ "hidden": false
+}
\ No newline at end of file
diff --git a/coder-apps/tests/spi_test/static/css/index.css b/coder-apps/tests/spi_test/static/css/index.css
new file mode 100644
index 00000000..e00b9e26
--- /dev/null
+++ b/coder-apps/tests/spi_test/static/css/index.css
@@ -0,0 +1,65 @@
+
+.pagecontent {
+ padding: 24px;
+ min-width: 880px;
+}
+
+.buttons {
+ float: left;
+ width: 240px;
+}
+.diagram {
+ width: 640px;
+ float: left;
+ padding-bottom: 100px;
+}
+.clear {
+ clear: both;
+}
+
+.reading {
+ margin-top: 20px;
+}
+
+#buttonval {
+ width: 200px;
+ height: 150px;
+ margin: 24px 0;
+ color: #fff;
+ background-color: #000000;
+ text-align: center;
+ font-weight: bold;
+ line-height: 150px;
+}
+#buttonval.on {
+ background-color: #F03050;
+}
+
+
+.button {
+ cursor: pointer;
+ width: 200px;
+ height: 150px;
+ margin: 24px 0;
+ background-color: #3498D8;
+ line-height: 150px;
+ text-align: center;
+ color: #fff;
+ font-weight: bold;
+}
+
+.button.press {
+ background-color: #2488A8;
+}
+
+
+#output {
+ position: fixed;
+ left:0;
+ right:0;
+ bottom:0;
+ height: 100px;
+ color: #fff;
+ background-color: #000;
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/coder-apps/tests/spi_test/static/js/index.js b/coder-apps/tests/spi_test/static/js/index.js
new file mode 100644
index 00000000..bd125ef6
--- /dev/null
+++ b/coder-apps/tests/spi_test/static/js/index.js
@@ -0,0 +1,61 @@
+///////////////////////
+// SPI Test
+// Sample code to interact with Raspberry Pi hardware, talk to an
+// MCP3008 ADC chip and return the analog value of channel 0.
+//
+// This part of the code runs in your web browser. It's responsible
+// for handling user input from the web browser, sending commands
+// to the Raspberry Pi, and listening for updates that the device
+// sends back.
+//
+// The code here communicates with another program that runs directly
+// on the Pi (not in your browser). That device-side code can be found
+// in the Node tab.
+////////////////////////
+
+$(document).ready( function() {
+
+ // Connection can take a second. Let the user know what's happening.
+ addOutputMessage( "Connecting... see the debug console for log messages." );
+
+ // This establishes a socket connection to the Coder device. A
+ // socket conection stays open while the user is viewing this page,
+ // which allows us to send a receive data very quickly from the device
+ // instead of checking for updates multiple times a second.
+ //
+ // Coder.socketConnection.init takes a callback function that will
+ // be executed once the connection is established. Anything that
+ // requires an established connection in order to function
+ // correctly should be placed in here.
+ Coder.socketConnection.init(function(){
+
+ // Each connection gets a unique ID.
+ addOutputMessage( "Connected with ID: " + Coder.socketConnection.socketID );
+
+ // Send a "connect" message to our Node page when we first connect.
+ Coder.socketConnection.sendData( 'connect', {} );
+
+
+
+ Coder.socketConnection.addListener( 'analogdata', function( d ){
+ console.log("analog value: " + d);
+ $('#analogdata').text( d );
+ //addOutputMessage( "Analog value: " + d );
+ });
+
+
+
+ });
+
+});
+
+
+
+
+// Append a new P tag to the #output DIV
+var addOutputMessage = function( text ) {
+ var $output = $("#output");
+ $output.append( $("").text( text ) );
+ console.log( text );
+};
+
diff --git a/coder-apps/tests/spi_test/static/media/.gitignore b/coder-apps/tests/spi_test/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-apps/tests/spi_test/views/index.html b/coder-apps/tests/spi_test/views/index.html
new file mode 100644
index 00000000..c3bbb41a
--- /dev/null
+++ b/coder-apps/tests/spi_test/views/index.html
@@ -0,0 +1,40 @@
+
+
+
+ Coder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
SPI Test
+
Analog value:
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coder-base/apps/.gitignore b/coder-base/apps/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/config.js.default b/coder-base/config.js.default
new file mode 100644
index 00000000..19052a53
--- /dev/null
+++ b/coder-base/config.js.default
@@ -0,0 +1,28 @@
+
+exports.listenIP = null; //Defaults to *
+exports.listenPort = '8081'; //the SSL port things run on
+exports.httpListenPort = '8080'; //this will all be redirected to SSL
+exports.cacheApps = true;
+exports.httpVisiblePort = '80'; //forwarded http port the user sees
+exports.httpsVisiblePort = '443'; //forwarded https port the user sees
+
+
+//SSL Info
+exports.country = "US";
+exports.state = "New York";
+exports.locale = "New York";
+exports.commonName = "coder.local";
+exports.subjectAltName = "DNS:192.168.0.1";
+
+
+//Experimental
+//
+//Status Server
+// This can be used in conjundtion with the sample findcoder
+// appengine project. It allows multiple Coders on the same
+// NAT network to be discoverable. Coder devices will ping the
+// external server with their internal IP, and the server
+// will list the devices for any requesting machine that
+// originates from the same external IP.
+exports.statusServer = '[yourpingserver].appspot.com';
+exports.enableStatusServer = false;
diff --git a/coder-base/config.js.localhost b/coder-base/config.js.localhost
new file mode 100644
index 00000000..3aa5890b
--- /dev/null
+++ b/coder-base/config.js.localhost
@@ -0,0 +1,28 @@
+
+exports.listenIP = '127.0.0.1'; //Defaults to *
+exports.listenPort = '8081'; //the SSL port things run on
+exports.httpListenPort = '8080'; //this will all be redirected to SSL
+exports.cacheApps = true;
+exports.httpVisiblePort = '8080'; //forwarded http port the user sees
+exports.httpsVisiblePort = '8081'; //forwarded https port the user sees
+
+
+//SSL Info
+exports.country = "US";
+exports.state = "New York";
+exports.locale = "New York";
+exports.commonName = "coder.local";
+exports.subjectAltName = "DNS:192.168.0.1";
+
+
+//Experimental
+//
+//Status Server
+// This can be used in conjundtion with the sample findcoder
+// appengine project. It allows multiple Coders on the same
+// NAT network to be discoverable. Coder devices will ping the
+// external server with their internal IP, and the server
+// will list the devices for any requesting machine that
+// originates from the same external IP.
+exports.statusServer = '[yourpingserver].appspot.com';
+exports.enableStatusServer = false;
diff --git a/coder-base/localserver.js b/coder-base/localserver.js
new file mode 100644
index 00000000..1ef8034f
--- /dev/null
+++ b/coder-base/localserver.js
@@ -0,0 +1,177 @@
+/**
+ * Coder for Raspberry Pi
+ * A simple platform for experimenting with web stuff.
+ * http://goo.gl/coder
+ *
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+var express = require('express');
+var io = require('socket.io');
+var net = require('http');
+var http = require('http');
+var https = require('https');
+var crypto = require('crypto');
+var path = require('path');
+var config = require('./config');
+var fs = require('fs');
+var util = require('util');
+var cons = require('consolidate');
+var params = require('express-params');
+var querystring = require('querystring');
+
+var loadApp = function( loadpath ) {
+
+ var userapp = null;
+ if ( config.cacheApps ) {
+ userapp = require(loadpath);
+ } else {
+
+ var cached = require.cache[loadpath + '.js'];
+ if ( cached ) {
+ userapp = require(loadpath);
+ if ( userapp.on_destroy ) {
+ userapp.on_destroy();
+ }
+ delete require.cache[loadpath + ".js"];
+ }
+ userapp = require(loadpath);
+ }
+ return userapp;
+};
+
+
+var apphandler = function( req, res, appdir ) {
+
+ var appname = req.params[0];
+ var apppath = req.params[1];
+ var modpath = appdir + appname;
+ var userapp = loadApp( modpath + "/app" );
+
+
+ util.log( "GET: " + apppath + " " + appname );
+
+ //Redirect to sign-in for unauthenticated users
+ publicAllowed = ["auth"]; //apps that are exempt from any login (should only be auth)
+ auth = require(appdir + "auth" + "/app");
+ user = auth.isAuthenticated(req, res);
+ if ( !user && publicAllowed.indexOf( appname ) < 0) {
+ util.log("redirect: " + "http://" + getHost(req) + ":" + config.httpVisiblePort + '/app/auth');
+ res.redirect("http://" + getHost(req) + ":" + config.httpVisiblePort + '/app/auth' );
+ return;
+ }
+
+
+ if ( !apppath ) {
+ apppath = "/";
+ } else {
+ apppath = "/" + apppath;
+ }
+
+ userapp.settings.appname = appname;
+ userapp.settings.viewpath="apps/" + appname;
+ userapp.settings.appurl="/app/" + appname;
+ userapp.settings.staticurl = "/static/apps/" + appname;
+ userapp.settings.device_name = auth.getDeviceName();
+ userapp.settings.coder_owner = auth.getCoderOwner();
+ userapp.settings.coder_color = auth.getCoderColor();
+ if ( userapp.settings.device_name === "" ) {
+ userapp.settings.device_name = "Coder";
+ }
+ if ( userapp.settings.coder_color === "" ) {
+ userapp.settings.coder_color = "#3e3e3e";
+ }
+
+ var routes = [];
+ if ( req.route.method === 'get' ) {
+ routes = userapp.get_routes;
+ } else if ( req.route.method === 'post' ) {
+ routes = userapp.post_routes;
+ }
+
+ if ( routes ) {
+ var found = false;
+ for ( var i in routes ) {
+ route = routes[i];
+ if ( route['path'] instanceof RegExp ) {
+ var m = route['path'].exec( apppath );
+ if ( m ) {
+ userapp[route['handler']]( req, res, m );
+ found = true;
+ break;
+ }
+
+ } else if ( route['path'] === apppath ) {
+ userapp[route['handler']]( req, res );
+ found = true;
+ break;
+ }
+
+ }
+
+ if ( !found ) {
+ res.status( 404 );
+ res.render('404', {
+ title: 'error'
+ });
+ }
+ }
+};
+
+
+var startLocal = function() {
+ http.createServer(localapp).listen( config.httpListenPort, '127.0.0.1' );
+};
+
+var getHost = function( req ) {
+ var host = req.connection.address().address;
+ if ( typeof req.headers.host !== "undefined" ) {
+ host = req.headers.host;
+ if ( host.match(/:/g) ) {
+ host = host.slice( 0, host.indexOf(":") );
+ }
+ }
+ return host;
+};
+
+//Traffic on 127.0.0.1 (localhost) only
+var localapp = express();
+params.extend( localapp );
+localapp.engine( 'html', cons.mustache );
+localapp.set( 'view engine', 'html' );
+localapp.set( 'views', __dirname + '/views' );
+localapp.use( express.bodyParser() );
+localapp.use( express.cookieParser() );
+localapp.use( express.session({
+ secret: crypto.randomBytes(16).toString('utf-8'),
+ store: new express.session.MemoryStore()
+}));
+localapp.use( '/static', express.static( __dirname + '/static' ) );
+localapp.get( '/', function( req, res ) {
+ util.log( 'GET: /' );
+ res.redirect( '/app/auth' );
+});
+localapp.all( /^\/app\/(\w+)\/(.*)$/, function( req, res ) { apphandler( req, res, __dirname + '/apps/'); } );
+localapp.all( /^\/app\/(\w+)\/$/, function( req, res ) { apphandler( req, res, __dirname + '/apps/'); } );
+localapp.all( /^\/app\/(\w+)$/, function( req, res ) { apphandler( req, res, __dirname + '/apps/'); } );
+
+
+startLocal();
+
+process.on('uncaughtException', function(err) {
+ console.log('WARNING: unhandled exception: ' + err );
+});
+
diff --git a/coder-base/package.json b/coder-base/package.json
index a107a3d6..8292e5d2 100644
--- a/coder-base/package.json
+++ b/coder-base/package.json
@@ -1,15 +1,17 @@
{
"name": "coder-base",
- "description": "kid-friendly web programming environment for pi",
- "version": "0.0.1",
- "private": true,
- "dependencies": {
- "express": "3.1.0",
- "redis": "0.8.2",
- "mustache": "0.7.2",
- "consolidate": "0.8.0",
- "socket.io": "0.9.13",
- "express-params": "0.0.3",
- "bcrypt": "0.7.4"
- }
+ "description": "A simple way to make cool web things",
+ "version": "0.0.7",
+ "private": true,
+ "dependencies": {
+ "express": "3.1.0",
+ "redis": "0.8.2",
+ "mustache": "0.7.2",
+ "consolidate": "0.8.0",
+ "socket.io": "0.9.13",
+ "express-params": "0.0.3",
+ "bcrypt-nodejs": "*",
+ "connect": "2.14.3",
+ "cookie": "0.1.1"
+ }
}
diff --git a/coder-base/server.js b/coder-base/server.js
index f7ec4f97..10c8f049 100644
--- a/coder-base/server.js
+++ b/coder-base/server.js
@@ -20,7 +20,7 @@
var express = require('express');
-var io = require('socket.io');
+var socketio = require('socket.io');
var net = require('http');
var http = require('http');
var https = require('https');
@@ -32,6 +32,10 @@ var util = require('util');
var cons = require('consolidate');
var params = require('express-params');
var querystring = require('querystring');
+var path = require('path');
+var cookie = require('cookie');
+var connect = require('connect');
+
var loadApp = function( loadpath ) {
@@ -53,6 +57,21 @@ var loadApp = function( loadpath ) {
return userapp;
};
+var applyAppSettings = function( userapp, appname, auth ) {
+ userapp.settings.appname = appname;
+ userapp.settings.viewpath="apps/" + appname;
+ userapp.settings.appurl="/app/" + appname;
+ userapp.settings.staticurl = "/static/apps/" + appname;
+ userapp.settings.device_name = auth.getDeviceName();
+ userapp.settings.coder_owner = auth.getCoderOwner();
+ userapp.settings.coder_color = auth.getCoderColor();
+ if ( userapp.settings.device_name === "" ) {
+ userapp.settings.device_name = "Coder";
+ }
+ if ( userapp.settings.coder_color === "" ) {
+ userapp.settings.coder_color = "#3e3e3e";
+ }
+};
var apphandler = function( req, res, appdir ) {
@@ -69,7 +88,8 @@ var apphandler = function( req, res, appdir ) {
auth = require(appdir + "auth" + "/app");
user = auth.isAuthenticated(req, res);
if ( !user && publicAllowed.indexOf( appname ) < 0) {
- res.redirect("https://" + getHost(req) + ":" + config.httpsVisiblePort + '/app/auth' );
+ util.log( "redirect: " + "https://" + getHost(req) + ":" + config.httpsVisiblePort + '/app/auth' );
+ res.redirect("https://" + getHost(req) + ":" + config.httpsVisiblePort + '/app/auth' );
return;
}
@@ -80,19 +100,7 @@ var apphandler = function( req, res, appdir ) {
apppath = "/" + apppath;
}
- userapp.settings.appname = appname;
- userapp.settings.viewpath="apps/" + appname;
- userapp.settings.appurl="/app/" + appname;
- userapp.settings.staticurl = "/static/apps/" + appname;
- userapp.settings.device_name = auth.getDeviceName();
- userapp.settings.coder_owner = auth.getCoderOwner();
- userapp.settings.coder_color = auth.getCoderColor();
- if ( userapp.settings.device_name === "" ) {
- userapp.settings.device_name = "Coder";
- }
- if ( userapp.settings.coder_color === "" ) {
- userapp.settings.coder_color = "#3e3e3e";
- }
+ applyAppSettings( userapp, appname, auth );
var routes = [];
if ( req.route.method === 'get' ) {
@@ -134,49 +142,174 @@ var startSSLRedirect = function() {
http.createServer( redirectapp ).listen( config.httpListenPort, config.listenIP );
};
+var server;
var startSSL = function() {
+ privateKeyFile=path.normalize('certs/server.key');
+ certificateFile=path.normalize('certs/server.cert');
var privateKey="";
var certificate="";
try {
- privateKey = fs.readFileSync('certs/server.key').toString();
- certificate = fs.readFileSync('certs/server.cert').toString();
+ privateKey = fs.readFileSync(privateKeyFile).toString();
+ certificate = fs.readFileSync(certificateFile).toString();
} catch ( e ) {
util.print( "no certificate found. generating self signed cert.\n" );
}
if ( privateKey !== "" && certificate !== "" ) {
- https.createServer({ key: privateKey, cert: certificate }, sslapp).listen( config.listenPort, config.listenIP );
+ server = https.createServer({ key: privateKey, cert: certificate }, sslapp);
+ server.listen( config.listenPort, config.listenIP );
+ initSocketIO( server );
} else {
var spawn = require('child_process').spawn;
- var genSelfSignedCert = function() {
+ var genSelfSignedCert = function(keyFile, certFile) {
var genkey = spawn( 'openssl', [
'req', '-x509', '-nodes',
'-days', '365',
'-newkey', 'rsa:2048',
- '-keyout', 'certs/server.key',
- '-out', 'certs/server.cert',
+ '-keyout', keyFile,
+ '-out', certFile,
'-subj',
'/C=' + config.country + '/ST=' + config.state + "/L=" + config.locale + "/CN=" + config.commonName + "/subjectAltName=" + config.subjectAltName
]);
genkey.stdout.on('data', function(d) { util.print(d) } );
genkey.stderr.on('data', function(d) { util.print(d) } );
genkey.addListener( 'exit', function( code, signal ) {
- fs.chmodSync('certs/server.key', '600');
+ fs.chmodSync(privateKeyFile, '600');
loadServer();
});
};
var loadServer = function() {
- privateKey = fs.readFileSync('certs/server.key').toString();
- certificate = fs.readFileSync('certs/server.cert').toString();
- https.createServer({ key: privateKey, cert: certificate }, sslapp).listen( config.listenPort, config.listenIP );
+ privateKey = fs.readFileSync(privateKeyFile).toString();
+ certificate = fs.readFileSync(certificateFile).toString();
+ server = https.createServer({ key: privateKey, cert: certificate }, sslapp);
+ server.listen( config.listenPort, config.listenIP );
+ initSocketIO( server );
};
- genSelfSignedCert();
+ genSelfSignedCert(privateKeyFile, certificateFile);
}
};
+var io;
+var socketMap={};
+var initSocketIO = function( server ) {
+ io = socketio.listen( server );
+ io.set('log level', 1); //TODO: hack to fix recursion problem since we are piping log info to a socket
+
+ // sync session data with socket
+ // via https://github.com/DanielBaulig/sioe-demo/blob/master/app.js
+ io.set('authorization', function (handshake, accept) {
+ if (!handshake.headers.cookie) {
+ console.log('no cookie sent with socket connection');
+ return accept('No cookie transmitted.', false);
+ }
+
+ handshake.cookie = cookie.parse(handshake.headers.cookie);
+ handshake.sessionID = connect.utils.parseSignedCookie(handshake.cookie['connect.sid'], storesecret);
+
+ if (handshake.cookie['connect.sid'] == handshake.sessionID) {
+ return accept('Cookie is invalid', false );
+ }
+
+ handshake.sessionStore = sslapp.sessionStore;
+
+ if (!handshake.sessionID) {
+ return accept('Session cookie could not be found', false);
+ }
+
+ handshake.sessionStore.get(handshake.sessionID, function (err, session) {
+ if (err) {
+ console.log( 'error loading session' );
+ return accept('Error', false);
+ }
+
+ var s = handshake.session = new express.session.Session(handshake, session );
+ return accept(null, true);
+ });
+ });
+
+
+ var genRandomID = function() {
+ var id = "";
+ var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+ for( var i=0; i < 32; i++ ) {
+ id += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return id;
+ }
+
+
+ io.sockets.on('connection', function (socket) {
+
+ var sess = socket.handshake.session;
+
+ socket.socketID = genRandomID();
+ socketMap[socket.socketID] = socket;
+ socket.emit('SOCKETID', socket.socketID);
+
+ socket.on('disconnect', function() {
+ delete( socketMap[socket.socketID] );
+ });
+
+ socket.on('appdata', function(data) {
+ if ( !sess.authenticated ) {
+ return;
+ }
+ if ( data.appid !== undefined && data.appid.match(/^\w+$/) && data.key !== undefined ) {
+ var appname = data.appid;
+ var userapp = loadApp( __dirname + '/apps/' + appname + "/app" );
+ var auth = require( __dirname + "/apps/auth/app" );
+ applyAppSettings( userapp, appname, auth );
+
+ var route;
+ var key = data.key;
+ var routes = userapp.socketio_routes;
+ if ( routes ) {
+ var found = false;
+ for ( var i in routes ) {
+ route = routes[i];
+ if ( route['key'] instanceof RegExp ) {
+ var m = route['path'].exec( key );
+ if ( m ) {
+ userapp[route['handler']]( socket, data.data, m );
+ found = true;
+ break;
+ }
+
+ } else if ( route['key'] === key ) {
+ userapp[route['handler']]( socket, data.data );
+ found = true;
+ break;
+ }
+
+ }
+ }
+ }
+ });
+ });
+};
+// Allow front end console to receive server logs over a socket connection.
+// Note that util.log will still only go to stdout
+var origlog = console.log;
+console.log = function(d) {
+ origlog.call( console, d );
+ if ( io ) {
+ io.set('log level', 1);
+ var clients = io.sockets.clients();
+ for ( var x=0; x " + logfile + " \" with administrator privileges'"
+ command = "osascript -e 'do shell script \"\\\"" + pythonexe + "\\\" -u formatsdcard.py really " + str( sdCardDev ) + " > " + logfile + " \" with administrator privileges'"
print( "SYSTEM: " + command )
#os.system( command )
diff --git a/installer/macosx/formatsdcard.py b/installer/macosx/formatsdcard.py
index 9706d2de..a0a03e36 100644
--- a/installer/macosx/formatsdcard.py
+++ b/installer/macosx/formatsdcard.py
@@ -50,7 +50,7 @@
filesize = os.path.getsize( filepath )
progresssize = 0
- command = 'dd bs=2m if=' + filepath + ' of=/dev/rdisk' + str( sdCardDev )
+ command = 'dd bs=2m if="' + filepath + '" of=/dev/rdisk' + str( sdCardDev )
print( "FORMATTING: " + command )
proc = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
diff --git a/installer/stock_raspbian/coder_bootstrap_install.sh b/installer/stock_raspbian/coder_bootstrap_install.sh
new file mode 100755
index 00000000..15042e6f
--- /dev/null
+++ b/installer/stock_raspbian/coder_bootstrap_install.sh
@@ -0,0 +1,36 @@
+
+echo "### Set up coder account."
+adduser --system --group coder
+echo ""
+
+
+echo "### Fetch the latest coder tree and install in /home/coder/coder-dist"
+su -s/bin/bash coder <<'EOF'
+cd /home/coder
+git clone https://github.com/googlecreativelab/coder.git coder-dist
+EOF
+echo ""
+
+echo "### Changing directory to raspian install scripts."
+echo "### /home/coder/coder-dist/installer/stock_raspbian/scripts"
+cd /home/coder/coder-dist/installer/stock_raspbian/scripts
+echo ""
+
+cat </etc/resolv.conf
+echo ""
+
+echo "Resetting wifi and network defaults."
+cp ../../../raspbian-addons/etc/network/interfaces /etc/network/interfaces
+cp ../../../raspbian-addons/etc/network/interfaces.reset /etc/network/interfaces.reset
+chown root:root /etc/network/interfaces
+chown root:root /etc/network/interfaces.reset
+chmod 664 /etc/network/interfaces
+chmod 664 /etc/network/interfaces.reset
+cp ../../../raspbian-addons/etc/wpa_supplicant/wpa_supplicant.conf.reset /etc/wpa_supplicant/wpa_supplicant.conf
+chown root:wpaconfig /etc/wpa_supplicant/wpa_supplicant.conf
+chmod 660 /etc/wpa_supplicant/wpa_supplicant.conf
+echo ""
+
+echo "Clearing system log files."
+rm /var/log/messages
+rm /var/log/syslog
+rm /var/log/wtmp
+touch /var/log/wtmp
+chmod 644 /var/log/wtmp
+rm /var/log/dmesg*
+rm /var/log/debug
+touch /var/log/debug
+rm /var/log/btmp
+touch /var/log/btmp
+chmod 644 /var/log/btmp
+rm /var/log/auth.log
+touch /var/log/auth.log
+chown root:adm /var/log/auth.log
+chmod 640 /var/log/auth.log
+touch /var/log/user.log
+chown root:adm /var/log/user.log
+chmod 640 /var/log/user.log
+echo ""
+
+# Reset pi password to raspberry
+echo "Choose the default pi passwd (normally this should be raspberry)"
+passwd pi
+
+echo ""
+echo "Done!"
+echo ""
+
diff --git a/installer/stock_raspbian/scripts/coder_system_setup.sh b/installer/stock_raspbian/scripts/coder_system_setup.sh
new file mode 100755
index 00000000..1477b423
--- /dev/null
+++ b/installer/stock_raspbian/scripts/coder_system_setup.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+echo "### Setting up the coder account."
+adduser --system --group coder
+echo ""
+
+
+echo "### Downloading the Coder git repo to /home/coder/coder-dist."
+su -s/bin/bash coder <<'EOF'
+cd /home/coder
+git clone https://github.com/googlecreativelab/coder.git coder-dist
+EOF
+echo ""
+
+
diff --git a/installer/stock_raspbian/scripts/grant_coder_sudo.sh b/installer/stock_raspbian/scripts/grant_coder_sudo.sh
new file mode 100755
index 00000000..84b232e5
--- /dev/null
+++ b/installer/stock_raspbian/scripts/grant_coder_sudo.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# Allows the coder user to run a limited number of scripts as the root user.
+# This is used for changing the pi password and wireless settings, and for
+# rebooting the device from the Coder UI.
+
+echo "### Granting sudo access to coder for scripts in /home/coder/coder-dist/coder-base/sudo_scripts/"
+bash -c "echo 'coder ALL= NOPASSWD: /home/coder/coder-dist/coder-base/sudo_scripts/*' >>/etc/sudoers"
+echo "### A line has been added to /etc/sudoers:"
+echo "coder ALL= NOPASSWD: /home/coder/coder-dist/coder-base/sudo_scripts/*"
+echo ""
diff --git a/installer/stock_raspbian/scripts/install_all_coder.sh b/installer/stock_raspbian/scripts/install_all_coder.sh
new file mode 100755
index 00000000..0676b86a
--- /dev/null
+++ b/installer/stock_raspbian/scripts/install_all_coder.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+cat <
+
+
+
+
+ Coder Server at %h.local
+
+
+ _http._tcp
+ 80
+
+
diff --git a/raspbian-addons/etc/hostname b/raspbian-addons/etc/hostname
new file mode 100644
index 00000000..972bf968
--- /dev/null
+++ b/raspbian-addons/etc/hostname
@@ -0,0 +1 @@
+coder
diff --git a/raspbian-addons/etc/hosts b/raspbian-addons/etc/hosts
new file mode 100644
index 00000000..c5997eaa
--- /dev/null
+++ b/raspbian-addons/etc/hosts
@@ -0,0 +1,8 @@
+127.0.0.1 localhost
+::1 localhost ip6-localhost ip6-loopback
+fe00::0 ip6-localnet
+ff00::0 ip6-mcastprefix
+ff02::1 ip6-allnodes
+ff02::2 ip6-allrouters
+
+127.0.1.1 coder
diff --git a/raspbian-addons/etc/init.d/coder-daemon b/raspbian-addons/etc/init.d/coder-daemon
index cf31ab81..34fc9940 100755
--- a/raspbian-addons/etc/init.d/coder-daemon
+++ b/raspbian-addons/etc/init.d/coder-daemon
@@ -17,7 +17,7 @@
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="Coder Server"
NAME="coder-daemon"
-DAEMON=/usr/bin/nodejs
+DAEMON=/opt/node/bin/node
DAEMON_ARGS="/home/coder/coder-dist/coder-base/server.js"
DAEMON_PATH="/home/coder/coder-dist/coder-base/"
PIDFILE=/var/run/$NAME.pid
diff --git a/raspbian-addons/etc/init.d/generate-ssh-hostkeys b/raspbian-addons/etc/init.d/generate-ssh-hostkeys
index 04ba4368..fe8d9cc2 100755
--- a/raspbian-addons/etc/init.d/generate-ssh-hostkeys
+++ b/raspbian-addons/etc/init.d/generate-ssh-hostkeys
@@ -16,21 +16,24 @@ logger="logger -t $prog"
rsa_key="/etc/ssh/ssh_host_rsa_key"
dsa_key="/etc/ssh/ssh_host_dsa_key"
+ecdsa_key="/etc/ssh/ssh_host_ecdsa_key"
# Exit if the hostkeys already exist
-if [ -f $rsa_key -a -f $dsa_key ]; then
+if [ -f $rsa_key -a -f $dsa_key -a -f $ecdsa_key ]; then
exit
fi
# Generate the ssh host keys
[ -f $rsa_key ] || ssh-keygen -f $rsa_key -t rsa -C 'host' -N ''
[ -f $dsa_key ] || ssh-keygen -f $dsa_key -t dsa -C 'host' -N ''
+[ -f $ecdsa_key ] || ssh-keygen -f $ecdsa_key -t ecdsa -C 'host' -N ''
# Output the public keys to the console
# This allows user to get host keys securely through console log
echo "-----BEGIN SSH HOST KEY FINGERPRINTS-----" | $logger
ssh-keygen -l -f $rsa_key.pub | $logger
ssh-keygen -l -f $dsa_key.pub | $logger
+ssh-keygen -l -f $ecdsa_key.pub | $logger
echo "------END SSH HOST KEY FINGERPRINTS------" | $logger
diff --git a/raspbian-addons/etc/init.d/isc-dhcp-server b/raspbian-addons/etc/init.d/isc-dhcp-server
index 59d88e2b..290b74f1 100755
--- a/raspbian-addons/etc/init.d/isc-dhcp-server
+++ b/raspbian-addons/etc/init.d/isc-dhcp-server
@@ -8,15 +8,15 @@
# Required-Stop: $remote_fs $network $syslog
# Should-Start: $local_fs slapd $named
# Should-Stop: $local_fs slapd
-# Default-Start:
-# Default-Stop:
+# Default-Start:
+# Default-Stop:
# Short-Description: DHCP server
# Description: Dynamic Host Configuration Protocol Server
### END INIT INFO
##commented out only launching from wpa-supplicant
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
+# ORIG-Start: 2 3 4 5
+# ORIG-Stop: 0 1 6
PATH=/sbin:/bin:/usr/sbin:/usr/bin
diff --git a/raspbian-addons/etc/init.d/pull-coder-reset b/raspbian-addons/etc/init.d/pull-coder-reset
index eb398c58..09990a77 100755
--- a/raspbian-addons/etc/init.d/pull-coder-reset
+++ b/raspbian-addons/etc/init.d/pull-coder-reset
@@ -19,6 +19,8 @@ source_wpa_conf="/etc/wpa_supplicant/wpa_supplicant.conf.reset"
dest_wpa_conf="/etc/wpa_supplicant/wpa_supplicant.conf"
source_device_json="/home/coder/coder-dist/coder-base/device.json.reset"
dest_device_json="/home/coder/coder-dist/coder-base/device.json"
+source_net_interfaces="/etc/network/interfaces.reset"
+dest_net_interfaces="/etc/network/interfaces"
# copy from source to dest if source exists
if [ -f $reset_file ]; then
@@ -26,6 +28,10 @@ if [ -f $reset_file ]; then
cp $source_wpa_conf $dest_wpa_conf
chown root:wpaconfig $dest_wpa_conf
chmod 660 $dest_wpa_conf
+ echo "-----RESET NETWORK INTERFACES-----" | $logger
+ cp $source_net_interfaces $dest_net_interfaces
+ chown root:root $dest_net_interfaces
+ chmod 664 $dest_net_interfaces
echo "-----RESET DEVICE.JSON-----" | $logger
cp $source_device_json $dest_device_json
chown coder $dest_device_json
diff --git a/raspbian-addons/etc/init.d/pull-hostname b/raspbian-addons/etc/init.d/pull-hostname
index 23db23fc..8d0ee36f 100755
--- a/raspbian-addons/etc/init.d/pull-hostname
+++ b/raspbian-addons/etc/init.d/pull-hostname
@@ -14,23 +14,29 @@
prog=$(basename $0)
logger="logger -t $prog"
-source_conf="/boot/coder_settings/hostname.txt"
-dest_conf="/etc/hostname"
+hostname_conf="/boot/coder_settings/hostname.txt"
+hostname_dest_conf="/etc/hostname"
+hosts_conf="/boot/coder_settings/hosts.txt"
+hosts_dest_conf="/etc/hosts"
# copy from source to dest if source exists
-if [ -f $source_conf ]; then
- echo "-----IMPORTING WPA_SUPPLICANT.CONF FROM SD-----" | $logger
- cp $source_conf $dest_conf
- chown root:root $dest_conf
- chmod 644 $dest_conf
+if [ -f $hostname_conf ]; then
+ echo "-----IMPORTING HOSTNAME FROM SD-----" | $logger
+ cp $hostname_conf $hostname_dest_conf
+ chown root:root $hostname_dest_conf
+ chmod 644 $hostname_dest_conf
HOSTNAME="$(cat /etc/hostname)"
- hostname "$HOSTNAME"
-
- # Should we delete or re-import every time?
- # Opting to import every time.
- # rm -f $source_conf
+ hostname "$HOSTNAME"
+fi
+
+# copy from source to dest if source exists
+if [ -f $hosts_conf ]; then
+ echo "-----IMPORTING HOSTS FROM SD-----" | $logger
+ cp $hosts_conf $hosts_dest_conf
+ chown root:root $hosts_dest_conf
+ chmod 644 $hosts_dest_conf
fi
diff --git a/raspbian-addons/etc/init.d/pull-net-interfaces b/raspbian-addons/etc/init.d/pull-net-interfaces
new file mode 100755
index 00000000..b239cebf
--- /dev/null
+++ b/raspbian-addons/etc/init.d/pull-net-interfaces
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides: pull-net-interfaces
+# Required-Start:
+# Required-Stop:
+# Should-Start:
+# Should-Stop:
+# Default-Start: S
+# Default-Stop:
+# Description: import /etc/network/interfaces from boot partition if it exists
+### END INIT INFO
+
+prog=$(basename $0)
+logger="logger -t $prog"
+
+source_conf="/boot/coder_settings/net_interfaces.txt"
+dest_conf="/etc/network/interfaces"
+
+# copy from source to dest if source exists
+if [ -f $source_conf ]; then
+ echo "-----IMPORTING NET INTERFACES FROM SD-----" | $logger
+ cp $source_conf $dest_conf
+ chown root:root $dest_conf
+ chmod 664 $dest_conf
+ rm -f $source_conf
+fi
+
+
+
diff --git a/raspbian-addons/etc/modprobe.d/8192cu.conf b/raspbian-addons/etc/modprobe.d/8192cu.conf
new file mode 100644
index 00000000..bac98b93
--- /dev/null
+++ b/raspbian-addons/etc/modprobe.d/8192cu.conf
@@ -0,0 +1 @@
+options 8192cu rtw_power_mgnt=0 rtw_enusbss=0 rtw_ips_mode=1
diff --git a/raspbian-addons/etc/modprobe.d/raspi-blacklist.conf b/raspbian-addons/etc/modprobe.d/raspi-blacklist.conf
new file mode 100644
index 00000000..61c637eb
--- /dev/null
+++ b/raspbian-addons/etc/modprobe.d/raspi-blacklist.conf
@@ -0,0 +1,4 @@
+# blacklist spi and i2c by default (many users don't need them)
+
+#blacklist spi-bcm2708
+#blacklist i2c-bcm2708
diff --git a/raspbian-addons/etc/modules b/raspbian-addons/etc/modules
new file mode 100644
index 00000000..40224950
--- /dev/null
+++ b/raspbian-addons/etc/modules
@@ -0,0 +1,18 @@
+# /etc/modules: kernel modules to load at boot time.
+#
+# This file contains the names of kernel modules that should be loaded
+# at boot time, one per line. Lines beginning with "#" are ignored.
+# Parameters can be specified after the module name.
+
+# Sound
+snd-bcm2835
+
+# SPI
+spi-bcm2708
+spi-dev
+
+# I2C
+i2c-bcm2708
+i2c-dev
+
+
diff --git a/raspbian-addons/etc/network/interfaces b/raspbian-addons/etc/network/interfaces
index a0c985d3..b66eebad 100644
--- a/raspbian-addons/etc/network/interfaces
+++ b/raspbian-addons/etc/network/interfaces
@@ -1,8 +1,26 @@
auto lo
iface lo inet loopback
+
+###
+# Set up ethernet to use dhcp
+###
iface eth0 inet dhcp
+###
+# Or comment the above and uncomment these
+# lines for a static IP address
+###
+#auto eth0
+#iface eth0 inet static
+# address 192.168.1.10
+# netmask 255.255.255.0
+# network 192.168.1.0
+# broadcast 192.168.1.255
+# gateway 192.168.1.1
+#
+
+
allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
diff --git a/raspbian-addons/etc/network/interfaces.reset b/raspbian-addons/etc/network/interfaces.reset
new file mode 100644
index 00000000..b66eebad
--- /dev/null
+++ b/raspbian-addons/etc/network/interfaces.reset
@@ -0,0 +1,36 @@
+auto lo
+
+iface lo inet loopback
+
+###
+# Set up ethernet to use dhcp
+###
+iface eth0 inet dhcp
+
+###
+# Or comment the above and uncomment these
+# lines for a static IP address
+###
+#auto eth0
+#iface eth0 inet static
+# address 192.168.1.10
+# netmask 255.255.255.0
+# network 192.168.1.0
+# broadcast 192.168.1.255
+# gateway 192.168.1.1
+#
+
+
+allow-hotplug wlan0
+iface wlan0 inet manual
+wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
+iface default inet dhcp
+
+iface staticadhoc inet static
+ address 192.168.0.1
+ netmask 255.255.255.0
+ network 192.168.0.0
+ broadcast 192.168.0.255
+ post-up /etc/init.d/isc-dhcp-server start
+ pre-down /etc/init.d/isc-dhcp-server stop
+
diff --git a/raspbian-addons/etc/redis/redis.conf b/raspbian-addons/etc/redis/redis.conf
new file mode 100644
index 00000000..2d5b35a4
--- /dev/null
+++ b/raspbian-addons/etc/redis/redis.conf
@@ -0,0 +1,492 @@
+# Redis configuration file example
+
+# Note on units: when memory size is needed, it is possible to specifiy
+# it in the usual form of 1k 5GB 4M and so forth:
+#
+# 1k => 1000 bytes
+# 1kb => 1024 bytes
+# 1m => 1000000 bytes
+# 1mb => 1024*1024 bytes
+# 1g => 1000000000 bytes
+# 1gb => 1024*1024*1024 bytes
+#
+# units are case insensitive so 1GB 1Gb 1gB are all the same.
+
+# By default Redis does not run as a daemon. Use 'yes' if you need it.
+# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
+daemonize yes
+
+# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
+# default. You can specify a custom pid file location here.
+pidfile /var/run/redis/redis-server.pid
+
+# Accept connections on the specified port, default is 6379.
+# If port 0 is specified Redis will not listen on a TCP socket.
+port 6379
+
+# If you want you can bind a single interface, if the bind option is not
+# specified all the interfaces will listen for incoming connections.
+#
+bind 127.0.0.1
+
+# Specify the path for the unix socket that will be used to listen for
+# incoming connections. There is no default, so Redis will not listen
+# on a unix socket when not specified.
+#
+# unixsocket /var/run/redis/redis.sock
+# unixsocketperm 755
+
+# Close the connection after a client is idle for N seconds (0 to disable)
+timeout 0
+
+# Set server verbosity to 'debug'
+# it can be one of:
+# debug (a lot of information, useful for development/testing)
+# verbose (many rarely useful info, but not a mess like the debug level)
+# notice (moderately verbose, what you want in production probably)
+# warning (only very important / critical messages are logged)
+loglevel notice
+
+# Specify the log file name. Also 'stdout' can be used to force
+# Redis to log on the standard output. Note that if you use standard
+# output for logging but daemonize, logs will be sent to /dev/null
+logfile /var/log/redis/redis-server.log
+
+# To enable logging to the system logger, just set 'syslog-enabled' to yes,
+# and optionally update the other syslog parameters to suit your needs.
+# syslog-enabled no
+
+# Specify the syslog identity.
+# syslog-ident redis
+
+# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
+# syslog-facility local0
+
+# Set the number of databases. The default database is DB 0, you can select
+# a different one on a per-connection basis using SELECT where
+# dbid is a number between 0 and 'databases'-1
+databases 16
+
+################################ SNAPSHOTTING #################################
+#
+# Save the DB on disk:
+#
+# save
+#
+# Will save the DB if both the given number of seconds and the given
+# number of write operations against the DB occurred.
+#
+# In the example below the behaviour will be to save:
+# after 900 sec (15 min) if at least 1 key changed
+# after 300 sec (5 min) if at least 10 keys changed
+# after 60 sec if at least 10000 keys changed
+#
+# Note: you can disable saving at all commenting all the "save" lines.
+
+save 900 1
+save 300 10
+save 60 10000
+
+# Compress string objects using LZF when dump .rdb databases?
+# For default that's set to 'yes' as it's almost always a win.
+# If you want to save some CPU in the saving child set it to 'no' but
+# the dataset will likely be bigger if you have compressible values or keys.
+rdbcompression yes
+
+# The filename where to dump the DB
+dbfilename dump.rdb
+
+# The working directory.
+#
+# The DB will be written inside this directory, with the filename specified
+# above using the 'dbfilename' configuration directive.
+#
+# Also the Append Only File will be created inside this directory.
+#
+# Note that you must specify a directory here, not a file name.
+dir /var/lib/redis
+
+################################# REPLICATION #################################
+
+# Master-Slave replication. Use slaveof to make a Redis instance a copy of
+# another Redis server. Note that the configuration is local to the slave
+# so for example it is possible to configure the slave to save the DB with a
+# different interval, or to listen to another port, and so on.
+#
+# slaveof
+
+# If the master is password protected (using the "requirepass" configuration
+# directive below) it is possible to tell the slave to authenticate before
+# starting the replication synchronization process, otherwise the master will
+# refuse the slave request.
+#
+# masterauth
+
+# When a slave lost the connection with the master, or when the replication
+# is still in progress, the slave can act in two different ways:
+#
+# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
+# still reply to client requests, possibly with out of data data, or the
+# data set may just be empty if this is the first synchronization.
+#
+# 2) if slave-serve-stale data is set to 'no' the slave will reply with
+# an error "SYNC with master in progress" to all the kind of commands
+# but to INFO and SLAVEOF.
+#
+slave-serve-stale-data yes
+
+# Slaves send PINGs to server in a predefined interval. It's possible to change
+# this interval with the repl_ping_slave_period option. The default value is 10
+# seconds.
+#
+# repl-ping-slave-period 10
+
+# The following option sets a timeout for both Bulk transfer I/O timeout and
+# master data or ping response timeout. The default value is 60 seconds.
+#
+# It is important to make sure that this value is greater than the value
+# specified for repl-ping-slave-period otherwise a timeout will be detected
+# every time there is low traffic between the master and the slave.
+#
+# repl-timeout 60
+
+################################## SECURITY ###################################
+
+# Require clients to issue AUTH before processing any other
+# commands. This might be useful in environments in which you do not trust
+# others with access to the host running redis-server.
+#
+# This should stay commented out for backward compatibility and because most
+# people do not need auth (e.g. they run their own servers).
+#
+# Warning: since Redis is pretty fast an outside user can try up to
+# 150k passwords per second against a good box. This means that you should
+# use a very strong password otherwise it will be very easy to break.
+#
+# requirepass foobared
+
+# Command renaming.
+#
+# It is possilbe to change the name of dangerous commands in a shared
+# environment. For instance the CONFIG command may be renamed into something
+# of hard to guess so that it will be still available for internal-use
+# tools but not available for general clients.
+#
+# Example:
+#
+# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
+#
+# It is also possilbe to completely kill a command renaming it into
+# an empty string:
+#
+# rename-command CONFIG ""
+
+################################### LIMITS ####################################
+
+# Set the max number of connected clients at the same time. By default there
+# is no limit, and it's up to the number of file descriptors the Redis process
+# is able to open. The special value '0' means no limits.
+# Once the limit is reached Redis will close all the new connections sending
+# an error 'max number of clients reached'.
+#
+# maxclients 128
+
+# Don't use more memory than the specified amount of bytes.
+# When the memory limit is reached Redis will try to remove keys
+# accordingly to the eviction policy selected (see maxmemmory-policy).
+#
+# If Redis can't remove keys according to the policy, or if the policy is
+# set to 'noeviction', Redis will start to reply with errors to commands
+# that would use more memory, like SET, LPUSH, and so on, and will continue
+# to reply to read-only commands like GET.
+#
+# This option is usually useful when using Redis as an LRU cache, or to set
+# an hard memory limit for an instance (using the 'noeviction' policy).
+#
+# WARNING: If you have slaves attached to an instance with maxmemory on,
+# the size of the output buffers needed to feed the slaves are subtracted
+# from the used memory count, so that network problems / resyncs will
+# not trigger a loop where keys are evicted, and in turn the output
+# buffer of slaves is full with DELs of keys evicted triggering the deletion
+# of more keys, and so forth until the database is completely emptied.
+#
+# In short... if you have slaves attached it is suggested that you set a lower
+# limit for maxmemory so that there is some free RAM on the system for slave
+# output buffers (but this is not needed if the policy is 'noeviction').
+#
+# maxmemory
+
+# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
+# is reached? You can select among five behavior:
+#
+# volatile-lru -> remove the key with an expire set using an LRU algorithm
+# allkeys-lru -> remove any key accordingly to the LRU algorithm
+# volatile-random -> remove a random key with an expire set
+# allkeys->random -> remove a random key, any key
+# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
+# noeviction -> don't expire at all, just return an error on write operations
+#
+# Note: with all the kind of policies, Redis will return an error on write
+# operations, when there are not suitable keys for eviction.
+#
+# At the date of writing this commands are: set setnx setex append
+# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
+# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
+# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
+# getset mset msetnx exec sort
+#
+# The default is:
+#
+# maxmemory-policy volatile-lru
+
+# LRU and minimal TTL algorithms are not precise algorithms but approximated
+# algorithms (in order to save memory), so you can select as well the sample
+# size to check. For instance for default Redis will check three keys and
+# pick the one that was used less recently, you can change the sample size
+# using the following configuration directive.
+#
+# maxmemory-samples 3
+
+############################## APPEND ONLY MODE ###############################
+
+# By default Redis asynchronously dumps the dataset on disk. If you can live
+# with the idea that the latest records will be lost if something like a crash
+# happens this is the preferred way to run Redis. If instead you care a lot
+# about your data and don't want to that a single record can get lost you should
+# enable the append only mode: when this mode is enabled Redis will append
+# every write operation received in the file appendonly.aof. This file will
+# be read on startup in order to rebuild the full dataset in memory.
+#
+# Note that you can have both the async dumps and the append only file if you
+# like (you have to comment the "save" statements above to disable the dumps).
+# Still if append only mode is enabled Redis will load the data from the
+# log file at startup ignoring the dump.rdb file.
+#
+# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
+# log file in background when it gets too big.
+
+appendonly yes
+
+# The name of the append only file (default: "appendonly.aof")
+# appendfilename appendonly.aof
+
+# The fsync() call tells the Operating System to actually write data on disk
+# instead to wait for more data in the output buffer. Some OS will really flush
+# data on disk, some other OS will just try to do it ASAP.
+#
+# Redis supports three different modes:
+#
+# no: don't fsync, just let the OS flush the data when it wants. Faster.
+# always: fsync after every write to the append only log . Slow, Safest.
+# everysec: fsync only if one second passed since the last fsync. Compromise.
+#
+# The default is "everysec" that's usually the right compromise between
+# speed and data safety. It's up to you to understand if you can relax this to
+# "no" that will will let the operating system flush the output buffer when
+# it wants, for better performances (but if you can live with the idea of
+# some data loss consider the default persistence mode that's snapshotting),
+# or on the contrary, use "always" that's very slow but a bit safer than
+# everysec.
+#
+# If unsure, use "everysec".
+
+# appendfsync always
+appendfsync everysec
+# appendfsync no
+
+# When the AOF fsync policy is set to always or everysec, and a background
+# saving process (a background save or AOF log background rewriting) is
+# performing a lot of I/O against the disk, in some Linux configurations
+# Redis may block too long on the fsync() call. Note that there is no fix for
+# this currently, as even performing fsync in a different thread will block
+# our synchronous write(2) call.
+#
+# In order to mitigate this problem it's possible to use the following option
+# that will prevent fsync() from being called in the main process while a
+# BGSAVE or BGREWRITEAOF is in progress.
+#
+# This means that while another child is saving the durability of Redis is
+# the same as "appendfsync none", that in pratical terms means that it is
+# possible to lost up to 30 seconds of log in the worst scenario (with the
+# default Linux settings).
+#
+# If you have latency problems turn this to "yes". Otherwise leave it as
+# "no" that is the safest pick from the point of view of durability.
+no-appendfsync-on-rewrite no
+
+# Automatic rewrite of the append only file.
+# Redis is able to automatically rewrite the log file implicitly calling
+# BGREWRITEAOF when the AOF log size will growth by the specified percentage.
+#
+# This is how it works: Redis remembers the size of the AOF file after the
+# latest rewrite (or if no rewrite happened since the restart, the size of
+# the AOF at startup is used).
+#
+# This base size is compared to the current size. If the current size is
+# bigger than the specified percentage, the rewrite is triggered. Also
+# you need to specify a minimal size for the AOF file to be rewritten, this
+# is useful to avoid rewriting the AOF file even if the percentage increase
+# is reached but it is still pretty small.
+#
+# Specify a precentage of zero in order to disable the automatic AOF
+# rewrite feature.
+
+auto-aof-rewrite-percentage 100
+auto-aof-rewrite-min-size 64mb
+
+################################## SLOW LOG ###################################
+
+# The Redis Slow Log is a system to log queries that exceeded a specified
+# execution time. The execution time does not include the I/O operations
+# like talking with the client, sending the reply and so forth,
+# but just the time needed to actually execute the command (this is the only
+# stage of command execution where the thread is blocked and can not serve
+# other requests in the meantime).
+#
+# You can configure the slow log with two parameters: one tells Redis
+# what is the execution time, in microseconds, to exceed in order for the
+# command to get logged, and the other parameter is the length of the
+# slow log. When a new command is logged the oldest one is removed from the
+# queue of logged commands.
+
+# The following time is expressed in microseconds, so 1000000 is equivalent
+# to one second. Note that a negative number disables the slow log, while
+# a value of zero forces the logging of every command.
+slowlog-log-slower-than 10000
+
+# There is no limit to this length. Just be aware that it will consume memory.
+# You can reclaim memory used by the slow log with SLOWLOG RESET.
+slowlog-max-len 128
+
+################################ VIRTUAL MEMORY ###############################
+
+### WARNING! Virtual Memory is deprecated in Redis 2.4
+### The use of Virtual Memory is strongly discouraged.
+
+# Virtual Memory allows Redis to work with datasets bigger than the actual
+# amount of RAM needed to hold the whole dataset in memory.
+# In order to do so very used keys are taken in memory while the other keys
+# are swapped into a swap file, similarly to what operating systems do
+# with memory pages.
+#
+# To enable VM just set 'vm-enabled' to yes, and set the following three
+# VM parameters accordingly to your needs.
+
+vm-enabled no
+# vm-enabled yes
+
+# This is the path of the Redis swap file. As you can guess, swap files
+# can't be shared by different Redis instances, so make sure to use a swap
+# file for every redis process you are running. Redis will complain if the
+# swap file is already in use.
+#
+# The best kind of storage for the Redis swap file (that's accessed at random)
+# is a Solid State Disk (SSD).
+#
+# *** WARNING *** if you are using a shared hosting the default of putting
+# the swap file under /tmp is not secure. Create a dir with access granted
+# only to Redis user and configure Redis to create the swap file there.
+vm-swap-file /var/lib/redis/redis.swap
+
+# vm-max-memory configures the VM to use at max the specified amount of
+# RAM. Everything that deos not fit will be swapped on disk *if* possible, that
+# is, if there is still enough contiguous space in the swap file.
+#
+# With vm-max-memory 0 the system will swap everything it can. Not a good
+# default, just specify the max amount of RAM you can in bytes, but it's
+# better to leave some margin. For instance specify an amount of RAM
+# that's more or less between 60 and 80% of your free RAM.
+vm-max-memory 0
+
+# Redis swap files is split into pages. An object can be saved using multiple
+# contiguous pages, but pages can't be shared between different objects.
+# So if your page is too big, small objects swapped out on disk will waste
+# a lot of space. If you page is too small, there is less space in the swap
+# file (assuming you configured the same number of total swap file pages).
+#
+# If you use a lot of small objects, use a page size of 64 or 32 bytes.
+# If you use a lot of big objects, use a bigger page size.
+# If unsure, use the default :)
+vm-page-size 32
+
+# Number of total memory pages in the swap file.
+# Given that the page table (a bitmap of free/used pages) is taken in memory,
+# every 8 pages on disk will consume 1 byte of RAM.
+#
+# The total swap size is vm-page-size * vm-pages
+#
+# With the default of 32-bytes memory pages and 134217728 pages Redis will
+# use a 4 GB swap file, that will use 16 MB of RAM for the page table.
+#
+# It's better to use the smallest acceptable value for your application,
+# but the default is large in order to work in most conditions.
+vm-pages 134217728
+
+# Max number of VM I/O threads running at the same time.
+# This threads are used to read/write data from/to swap file, since they
+# also encode and decode objects from disk to memory or the reverse, a bigger
+# number of threads can help with big objects even if they can't help with
+# I/O itself as the physical device may not be able to couple with many
+# reads/writes operations at the same time.
+#
+# The special value of 0 turn off threaded I/O and enables the blocking
+# Virtual Memory implementation.
+vm-max-threads 4
+
+############################### ADVANCED CONFIG ###############################
+
+# Hashes are encoded in a special way (much more memory efficient) when they
+# have at max a given numer of elements, and the biggest element does not
+# exceed a given threshold. You can configure this limits with the following
+# configuration directives.
+hash-max-zipmap-entries 512
+hash-max-zipmap-value 64
+
+# Similarly to hashes, small lists are also encoded in a special way in order
+# to save a lot of space. The special representation is only used when
+# you are under the following limits:
+list-max-ziplist-entries 512
+list-max-ziplist-value 64
+
+# Sets have a special encoding in just one case: when a set is composed
+# of just strings that happens to be integers in radix 10 in the range
+# of 64 bit signed integers.
+# The following configuration setting sets the limit in the size of the
+# set in order to use this special memory saving encoding.
+set-max-intset-entries 512
+
+# Similarly to hashes and lists, sorted sets are also specially encoded in
+# order to save a lot of space. This encoding is only used when the length and
+# elements of a sorted set are below the following limits:
+zset-max-ziplist-entries 128
+zset-max-ziplist-value 64
+
+# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
+# order to help rehashing the main Redis hash table (the one mapping top-level
+# keys to values). The hash table implementation redis uses (see dict.c)
+# performs a lazy rehashing: the more operation you run into an hash table
+# that is rhashing, the more rehashing "steps" are performed, so if the
+# server is idle the rehashing is never complete and some more memory is used
+# by the hash table.
+#
+# The default is to use this millisecond 10 times every second in order to
+# active rehashing the main dictionaries, freeing memory when possible.
+#
+# If unsure:
+# use "activerehashing no" if you have hard latency requirements and it is
+# not a good thing in your environment that Redis can reply form time to time
+# to queries with 2 milliseconds delay.
+#
+# use "activerehashing yes" if you don't have such hard requirements but
+# want to free memory asap when possible.
+activerehashing yes
+
+################################## INCLUDES ###################################
+
+# Include one or more other config files here. This is useful if you
+# have a standard template that goes to all redis server but also need
+# to customize a few per-server settings. Include files can include
+# other files, so use this wisely.
+#
+# include /path/to/local.conf
+# include /path/to/other.conf
diff --git a/raspbian-addons/etc/ssh/sshd_config b/raspbian-addons/etc/ssh/sshd_config
deleted file mode 100644
index 5458c7e9..00000000
--- a/raspbian-addons/etc/ssh/sshd_config
+++ /dev/null
@@ -1,87 +0,0 @@
-# Package generated configuration file
-# See the sshd_config(5) manpage for details
-
-# What ports, IPs and protocols we listen for
-Port 22
-# Use these options to restrict which interfaces/protocols sshd will bind to
-#ListenAddress ::
-#ListenAddress 0.0.0.0
-Protocol 2
-# HostKeys for protocol version 2
-HostKey /etc/ssh/ssh_host_rsa_key
-HostKey /etc/ssh/ssh_host_dsa_key
-#HostKey /etc/ssh/ssh_host_ecdsa_key
-#Privilege Separation is turned on for security
-UsePrivilegeSeparation yes
-
-# Lifetime and size of ephemeral version 1 server key
-KeyRegenerationInterval 3600
-ServerKeyBits 768
-
-# Logging
-SyslogFacility AUTH
-LogLevel INFO
-
-# Authentication:
-LoginGraceTime 120
-PermitRootLogin yes
-StrictModes yes
-
-RSAAuthentication yes
-PubkeyAuthentication yes
-#AuthorizedKeysFile %h/.ssh/authorized_keys
-
-# Don't read the user's ~/.rhosts and ~/.shosts files
-IgnoreRhosts yes
-# For this to work you will also need host keys in /etc/ssh_known_hosts
-RhostsRSAAuthentication no
-# similar for protocol version 2
-HostbasedAuthentication no
-# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
-#IgnoreUserKnownHosts yes
-
-# To enable empty passwords, change to yes (NOT RECOMMENDED)
-PermitEmptyPasswords no
-
-# Change to yes to enable challenge-response passwords (beware issues with
-# some PAM modules and threads)
-ChallengeResponseAuthentication no
-
-# Change to no to disable tunnelled clear text passwords
-#PasswordAuthentication yes
-
-# Kerberos options
-#KerberosAuthentication no
-#KerberosGetAFSToken no
-#KerberosOrLocalPasswd yes
-#KerberosTicketCleanup yes
-
-# GSSAPI options
-#GSSAPIAuthentication no
-#GSSAPICleanupCredentials yes
-
-X11Forwarding yes
-X11DisplayOffset 10
-PrintMotd no
-PrintLastLog yes
-TCPKeepAlive yes
-#UseLogin no
-
-#MaxStartups 10:30:60
-#Banner /etc/issue.net
-
-# Allow client to pass locale environment variables
-AcceptEnv LANG LC_*
-
-Subsystem sftp /usr/lib/openssh/sftp-server
-
-# Set this to 'yes' to enable PAM authentication, account processing,
-# and session processing. If this is enabled, PAM authentication will
-# be allowed through the ChallengeResponseAuthentication and
-# PasswordAuthentication. Depending on your PAM configuration,
-# PAM authentication via ChallengeResponseAuthentication may bypass
-# the setting of "PermitRootLogin without-password".
-# If you just want the PAM account and session checks to run without
-# PAM authentication, then enable this but set PasswordAuthentication
-# and ChallengeResponseAuthentication to 'no'.
-UsePAM yes
diff --git a/raspbian-addons/etc/sudoers b/raspbian-addons/etc/sudoers
index ba5fa1c7..7caa1d4a 100644
--- a/raspbian-addons/etc/sudoers
+++ b/raspbian-addons/etc/sudoers
@@ -27,4 +27,4 @@ root ALL=(ALL:ALL) ALL
#includedir /etc/sudoers.d
pi ALL=(ALL) NOPASSWD: ALL
-coder ALL= NOPASSWD: /home/coder/coder-dist/coder-base/sudo_scripts/setpipass
+coder ALL= NOPASSWD: /home/coder/coder-dist/coder-base/sudo_scripts/*
diff --git a/raspbian-addons/etc/udev/rules.d/10-gpio.rules b/raspbian-addons/etc/udev/rules.d/10-gpio.rules
new file mode 100644
index 00000000..fea8859d
--- /dev/null
+++ b/raspbian-addons/etc/udev/rules.d/10-gpio.rules
@@ -0,0 +1,5 @@
+# Give the GPIO group access to /sys/class/gpio*
+
+SUBSYSTEM=="gpio", KERNEL!="gpio[0-9]*", ACTION=="add", PROGRAM="/bin/bash -c 'chown -R root:gpio $sys/class/gpio ; chmod 220 $sys/class/gpio/{export,unexport}'"
+
+SUBSYSTEM=="gpio", ACTION=="add", PROGRAM="/bin/bash -c 'chmod -f 755 $sys$devpath ; chmod -f 660 $sys$devpath/{active_low,direction,edge,uevent,value} ; chown -Rf root:gpio $sys/$devpath'"
diff --git a/raspbian-addons/home/coder/coder-dist/coder-base/package.json b/raspbian-addons/home/coder/coder-dist/coder-base/package.json
new file mode 100644
index 00000000..80ae62b8
--- /dev/null
+++ b/raspbian-addons/home/coder/coder-dist/coder-base/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "coder-base",
+ "description": "A simple way to make cool web things with Raspberry Pi",
+ "version": "0.0.7",
+ "private": true,
+ "dependencies": {
+ "express": "3.1.0",
+ "redis": "0.8.2",
+ "mustache": "0.7.2",
+ "consolidate": "0.8.0",
+ "socket.io": "0.9.13",
+ "express-params": "0.0.3",
+ "bcrypt": "0.7.4",
+ "connect": "2.14.3",
+ "cookie": "0.1.1",
+ "gpio": "git://github.com/jmstriegel/GpiO.git",
+ "i2c": "*",
+ "pi-spi": "*"
+ }
+}
diff --git a/coder-base/sudo_scripts/reboot b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/reboot
similarity index 100%
rename from coder-base/sudo_scripts/reboot
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/reboot
diff --git a/coder-base/sudo_scripts/setpipass b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/setpipass
similarity index 100%
rename from coder-base/sudo_scripts/setpipass
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/setpipass
diff --git a/coder-base/sudo_scripts/wpa_cli_scan b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_apscan
similarity index 100%
rename from coder-base/sudo_scripts/wpa_cli_scan
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_apscan
diff --git a/coder-base/sudo_scripts/wpa_cli_apscan b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_scan
similarity index 100%
rename from coder-base/sudo_scripts/wpa_cli_apscan
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_scan
diff --git a/coder-base/sudo_scripts/wpa_cli_scanresults b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_scanresults
similarity index 100%
rename from coder-base/sudo_scripts/wpa_cli_scanresults
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_scanresults