Compare commits

...

79 Commits
v1.0 ... master

Author SHA1 Message Date
AJ ONeal e2e99a5c77 merge 2018-05-17 10:10:51 +00:00
AJ ONeal 712e583183 update urls 2018-05-17 10:09:43 +00:00
AJ ONeal bb48c635e2 typo fix on syntax 2018-04-25 00:10:07 +00:00
AJ ONeal 567c1cf39f update version, duh 2017-11-10 17:04:26 -07:00
AJ ONeal b803229dac Merge branch 'v1.2' of git.daplie.com:Daplie/walnut.js into v1.2 2017-11-10 12:52:33 -07:00
AJ ONeal ed9b05913e update urls 2017-11-10 12:52:27 -07:00
AJ ONeal 20eccd5f31 Merge branch 'master' into 'v1.2'
use updated oauth3

See merge request !4
2017-11-08 12:59:36 -07:00
AJ ONeal 4914b28b08 Update install.sh 2017-11-08 12:58:38 -07:00
AJ ONeal ff95bfedb8 Update README.md 2017-11-07 16:47:44 -07:00
AJ ONeal 5956aaf2ec fix var 2017-11-07 16:23:25 -07:00
AJ ONeal f2ad6f127c use sudo_cmd as needed 2017-11-07 16:20:55 -07:00
AJ ONeal 1dcb8d04a5 keep root ownership and default perms on / 2017-11-07 16:14:37 -07:00
AJ ONeal dc31325a0d Merge branch 'v1.2' 2017-11-07 16:01:35 -07:00
AJ ONeal d3951d7c6a fix installer once and for all? 2017-11-07 16:00:56 -07:00
AJ ONeal 1a27ffa6ad merge 2017-11-07 15:02:25 -07:00
AJ ONeal 485f8ce60c don't change existing files and folders 2017-11-07 15:01:27 -07:00
AJ ONeal 9707402e31 update install script 2017-11-07 14:57:40 -07:00
AJ ONeal 4ff4e44bc3 ignore tmpfiles.d 2017-11-07 14:32:28 -07:00
AJ ONeal faaf973170 place our node path BEFORE theirs 2017-11-07 12:28:21 -07:00
AJ ONeal 8e62ec3772 don't exit with bad status code 2017-11-07 12:15:40 -07:00
AJ ONeal dc58afaab0 remove quote to fix bash expansion 2017-11-07 05:19:47 -07:00
AJ ONeal 494953ce7e fix symlink 2017-11-07 05:15:04 -07:00
AJ ONeal cfc6850a47 remove old installer 2017-11-07 05:12:46 -07:00
AJ ONeal af7103e17b note unistall instructions 2017-11-07 05:10:26 -07:00
AJ ONeal 51c5976f11 use correct paths 2017-11-07 05:06:59 -07:00
AJ ONeal aea6853822 template for MY_USER and MY_GROUP 2017-11-07 05:02:14 -07:00
AJ ONeal 6ee3b60f84 use opt instead of /srv for walnut bins 2017-11-07 04:59:48 -07:00
AJ ONeal 2def719455 clone launchpad, duh 2017-11-07 04:43:36 -07:00
AJ ONeal 2a7102470e Merge branch 'v1.2' into installer-v2 2017-11-07 04:38:31 -07:00
AJ ONeal 952ec8d23b ursa is not a dependency 2017-11-07 04:38:10 -07:00
AJ ONeal efa5449662 source relative to git dir 2017-11-07 04:26:18 -07:00
AJ ONeal 936f458d79 create walnut.example.yml 2017-11-07 04:23:45 -07:00
AJ ONeal 3ef094b78c create bin dir before linking ;) 2017-11-07 04:22:59 -07:00
AJ ONeal fcc3cc7366 move my_app_name 2017-11-07 04:21:56 -07:00
AJ ONeal 058ec8b22f move my_tmp 2017-11-07 04:20:27 -07:00
AJ ONeal c37727e5d7 Merge branch 'v1.2' into installer-v2 2017-11-07 04:17:34 -07:00
AJ ONeal 76e882c572 bump ver, add standard files 2017-11-07 04:17:05 -07:00
AJ ONeal f843393fc6 WIP installer v2 2017-11-07 04:15:02 -07:00
AJ ONeal d7068b825c Update install.sh 2017-11-03 15:34:40 -06:00
AJ ONeal 517923b258 Update to node.js v8.9.0 2017-11-01 18:43:57 -06:00
aj abe88da1c9 install up-to-date launchpad version 2017-11-01 21:51:03 +00:00
aj 2bb33b1f20 update with version number 2017-10-23 23:26:32 +00:00
aj 6c182d4736 Merge branch 'master' into v1.2 2017-10-23 23:21:01 +00:00
aj 7f653e4d7a Merge branch 'issuer-rewrite' into ajs-update 2017-10-23 23:18:47 +00:00
tigerbot 5ae6a374fe added some dependencies to package.json
searched through the code with a `git grep` to try finding all
non-relative requires in non-browser / non-test code
2017-10-23 15:36:48 -06:00
tigerbot 82e1f10d04 updated with repo to clone for issuer@oauth3.org in the docs 2017-10-19 17:21:20 -06:00
tigerbot f4b172af01 Merge branch 'issuer-rewrite' 2017-10-19 17:05:04 -06:00
tigerbot 8076134d5f Merge branch 'ajs-update' into issuer-rewrite 2017-10-19 17:04:05 -06:00
Drew Warren ba2ed84b7a Update INSTALL.md 2017-10-06 18:03:03 -06:00
aj 976761b6e0 [WIP] retrieve sub from db 2017-09-12 22:32:33 +00:00
Drew Warren b221e5b5f2 Update INSTALL.md 2017-09-12 15:20:45 -06:00
tigerbot 08cb6c2d08 made request error logging more DRY 2017-09-12 11:55:47 -06:00
AJ ONeal fc604813e3 Update INSTALL.md 2017-09-11 12:48:31 -06:00
Drew Warren 8ad87bd779 Update INSTALL.md 2017-09-08 16:43:10 -06:00
Drew Warren ae9e6bc16d Update INSTALL.md 2017-09-08 16:20:52 -06:00
Drew Warren ae1b21bfb2 Update INSTALL.md 2017-09-08 14:55:33 -06:00
richdex14 3a6264939e Update install.sh to configure `bower` to allow usage under the root account, such as on single user systems such as our own. 2017-09-08 11:34:04 -06:00
richdex14 ec47516838 Update install.sh to install bower through NPM in case the system is missing it. 2017-09-08 11:13:44 -06:00
richdex14 dcdae5e0e6 Update walnut.service to use the `node` version installed as part of the walnut installation. 2017-09-07 10:32:49 -06:00
richdex14 34621cf288 Update install.sh to temporarily edit the `PATH` to ensure correct `node` and `npm` versions are used during installation. 2017-09-07 10:30:18 -06:00
richdex14 76193b3822 Update install-helper.sh to export `NODE_PATH` 2017-09-06 18:51:09 -06:00
tigerbot da68c102cd stop double printing uncaught error stacks 2017-09-06 13:02:28 -06:00
richdex14 ac5343c716 Update install-helper.sh to remove `v1` tag, which is outdated. 2017-09-04 14:44:55 -06:00
richdex14 ed24159574 Update install.sh to checkout master form walnut repo, `v1` tag outdated. 2017-09-04 13:42:38 -06:00
richdex14 32f835aa30 Update install.sh to use portable `sudo_cmd` syntax 2017-09-04 13:30:10 -06:00
aj 0492c66a8b restructure of some code 2017-09-01 00:49:08 +00:00
aj a429e48977 enable assets subdomain with cookies 2017-08-30 17:47:31 +00:00
aj babfb6b38b logging 2017-08-16 19:47:51 +00:00
tigerbot dae941323b added another place in token to find grants in tokens 2017-08-11 18:13:48 -06:00
tigerbot fa3816390b verify all tokens that are provided 2017-08-11 17:00:18 -06:00
tigerbot 92d052faf0 made req.oauth3 immutable after its creation 2017-08-11 16:38:22 -06:00
Drew Warren bb1ee7ab99 Update INSTALL.md 2017-08-10 12:46:44 -06:00
tigerbot de594964b4 added accountIdx and accountHash to req.oauth3 2017-08-10 11:09:39 -06:00
Drew Warren 1564655d2a Update INSTALL.md 2017-08-09 17:51:47 -06:00
Drew Warren 845989e16c Update INSTALL.md 2017-08-09 17:42:15 -06:00
AJ ONeal 2007fc0fa4 Update README.md 2017-08-09 14:54:43 -06:00
tigerbot a1f514a155 Merge branch 'master' into issuer-rewrite 2017-08-09 13:12:47 -06:00
tigerbot 4345725c83 made verifyAsync available to check other tokens (like refresh tokens) 2017-07-24 16:19:51 -06:00
tigerbot 5053963874 implemented verification of token signed elsewhere 2017-07-13 18:23:42 -06:00
26 changed files with 1764 additions and 1026 deletions

4
CHANGELOG Normal file
View File

@ -0,0 +1,4 @@
v1.2.5 - Beginning of CHANGELOG
* has semi-functional launchpad
* OAuth3 with issuer-rewrite merged in
* capabilities API

View File

@ -19,18 +19,19 @@ Pre-requisites:
* You own a domain
* through Daplie Domains
* or you understand domains and DNS and all that stuff
* Install bower `npm install -g bower`
Choose a domain
---------------
For the purpose of this instruction we'll assume that your domain is `example.com`,
For the purpose of this instruction we'll assume that your domain is `foo.com`,
but you can use, say, `johndoe.daplie.me` for testing through Daplie Domains.
Anyway, go ahead and set the bash variable `$my_domain` for the purposes of the
rest of this tutorial:
```
my_domain=example.com
my_domain=foo.com
```
You can purchase a domain with daplie tools
@ -47,17 +48,17 @@ Subdomains
Auth will be loaded with the following domains
```
provider.example.com
api.provider.example.com
provider.foo.com
api.provider.foo.com
```
The Hello World app will be loaded with the following domains
```
example.com
www.example.com
api.example.com
assets.example.com
foo.com
www.foo.com
api.foo.com
assets.foo.com
```
The domains can be setup through the Daplie Desktop App or with daplie-tools
@ -69,6 +70,9 @@ Replace `foodevice` with whatever you like to call this device
my_device=foodevice
# curl https://api.oauth3.org/api/tunnel@oauth3.org/checkip
# READ THIS: localhost is being used as an example.
# Your IP address should be public facing (i.e. port-forwarding is enabled on your router).
# If it isn't, then you need something like goldilocks providing a tunnel.
my_address=127.0.0.1
# set device address and attach primary domain
@ -92,7 +96,7 @@ Walnut must sit behind a proxy that properly terminates https and sets the `X-Fo
Goldilocks can do this, as well as manage daplie domains, tunneling, etc.
```bash
curl https://daplie.me/install-scripts | bash
curl https://git.daplie.com/Daplie/daplie-snippets/raw/master/install.sh | bash
daplie-install-goldilocks
```
@ -101,16 +105,16 @@ daplie-install-goldilocks
Example `/etc/goldilocks/goldilocks.yml`:
```yml
tls:
email: domains@example.com
email: user@mailservice.com
servernames:
- example.com
- www.example.com
- api.example.com
- assets.example.com
- cloud.example.com
- api.cloud.example.com
- provider.example.com
- api.provider.example.com
- foo.com
- www.foo.com
- api.foo.com
- assets.foo.com
- cloud.foo.com
- api.cloud.foo.com
- provider.foo.com
- api.provider.foo.com
http:
trust_proxy: true
@ -125,7 +129,7 @@ Basic Walnut Install
--------------------
```bash
curl https://daplie.me/install-scripts | bash
curl https://git.daplie.com/Daplie/daplie-snippets/raw/master/install.sh | bash
daplie-install-walnut
```
@ -162,8 +166,8 @@ Resetting the Initialization
Once you run the app the initialization files will appear in these locations
```
/srv/walnut/var/com.daplie.walnut.config.sqlite3
/srv/walnut/config/<domain.tld>/config.json
/srv/walnut/var/walnut+config@daplie.com.sqlite3
/srv/walnut/config/foo.com.json
```
Deleting those files and restarting walnut will reset it to its bootstrap state.
@ -188,7 +192,7 @@ For the APIs for that we'll install the `issuer@oauth3.org` API package and enab
```bash
# API packaged for walnut
git clone https://git.daplie.com/OAuth3/org.oauth3.provider.git /srv/walnut/packages/rest/issuer@oauth3.org
git clone https://git.daplie.com/OAuth3/issuer_oauth3.org.git /srv/walnut/packages/rest/issuer@oauth3.org
pushd /srv/walnut/packages/rest/issuer@oauth3.org/
git checkout v1.2
npm install
@ -224,13 +228,13 @@ It is intended to provide a way to use various mail services in the future,
just bear with us for the time being (or open a Merge Request).
```bash
vim /srv/walnut/var/$my_domain/config.json
mkdir -p /srv/walnut/var/provider.$my_domain
vim /srv/walnut/var/provider.$my_domain/config.json
```
```json
{ "mailgun.org": {
"apiKey": "key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
, "apiPublicKey": "pubkey-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
, "auth": {
"user": "robtherobot@example.com"
, "pass": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
@ -266,8 +270,9 @@ What it should look like:
models.js
rest.js
/srv/walnut/packages/client-api-grants/example.com
/srv/walnut/packages/client-api-grants/provider.foo.com
'''
issuer@oauth3.org
hello@example.com
'''
```

42
LICENSE
View File

@ -1,3 +1,41 @@
Copyright 2017 Daplie Inc.
Copyright 2017 Daplie, Inc
All Rights Reserved
This is open source software; you can redistribute it and/or modify it under the
terms of either:
a) the "MIT License"
b) the "Apache-2.0 License"
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Apache-2.0 License Summary
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.

View File

@ -19,7 +19,7 @@ Security Features
* disallows cookies, except for protected static assets
* api.* subdomain for apis
* assets.* subdomain for protected assets
* *must* sit behind a trusted https proxy (such as [Goldilocks](https://git.daplie.com/Daplie/goldilocks.js))
* *must* sit behind a trusted https proxy (such as [Goldilocks](https://git.coolaj86.com/coolaj86/goldilocks.js))
* HTTPS-only (checks for X-Forwarded-For)
* AES, RSA, and ECDSA encryption and signing
* Safe against CSRF, XSS, and SQL injection
@ -34,14 +34,14 @@ Application Features
* JSON-only expressjs APIs
* Capability-based permissions system for (oauth3-discoverable) packages such as
* large file access (files@daplie.com)
* database access (data@daplie.com)
* scheduling (for background tasks, alerts, alarms, calendars, reminders, etc) (events@daplie.com)
* payments (credit card) (payments@daplie.com)
* email (email@daplie.com)
* SMS (texting) (tel@daplie.com)
* voice (calls and answering machine) (tel@daplie.com)
* lamba-style functions (functions@daplie.com)
* large file access (files@oauth3.org)
* database access (data@oauth3.org)
* scheduling (for background tasks, alerts, alarms, calendars, reminders, etc) (events@oauth3.org)
* payments (credit card) (payments@oauth3.org)
* email (email@oauth3.org)
* SMS (texting) (tel@oauth3.org)
* voice (calls and answering machine) (tel@oauth3.org)
* lamba-style functions (functions@oauth3.org)
* Per-app, per-site, and per-user configurations
* Multi-Tentated Application Management
* Built-in OAuth2 & OAuth3 support
@ -53,8 +53,31 @@ Installation
We're still in a stage where the installation generally requires many manual steps.
```bash
curl https://git.coolaj86.com/coolaj86/walnut.js/raw/v1.2/installer/get.sh | bash
```
See [INSTALL.md](/INSTALL.md)
### Uninstall
```bash
rm -rf /srv/walnut/ /var/walnut/ /etc/walnut/ /opt/walnut/ /var/log/walnut/ /etc/systemd/system/walnut.service /etc/tmpfiles.d/walnut.conf
```
Usage
-----
Here's how you run the thing, once installed:
```
/opt/walnut/bin/node /srv/walnut/core/bin/walnut.js
```
It listens on all addresses, port 3000.
TODO: Add config to restrict listening to localhost.
API
---
@ -108,7 +131,7 @@ Initialization
needs to know its primary domain
```
POST https://api.<domain.tld>/api/walnut@daplie.com/init
POST https://api.<domain.tld>/api/walnut@oauth3.org/init
{ "domain": "<domain.tld>" }
```
@ -130,18 +153,18 @@ api.<domain.tld>
assets.<domain.tld>
```
The domains can be setup through the Daplie Desktop App or with `daplie-tools`
The domains can be setup through the OAuth3 Desktop App or with `oauth3-tools`
```bash
# set device address and attach primary domain
daplie devices:attach -d foodevice -n example.com -a 127.0.0.1
oauth3 devices:attach -d foodevice -n example.com -a 127.0.0.1
# attach all other domains with same device/address
daplie devices:attach -d foodevice -n www.example.com
daplie devices:attach -d foodevice -n api.example.com
daplie devices:attach -d foodevice -n assets.example.com
daplie devices:attach -d foodevice -n cloud.example.com
daplie devices:attach -d foodevice -n api.cloud.example.com
oauth3 devices:attach -d foodevice -n www.example.com
oauth3 devices:attach -d foodevice -n api.example.com
oauth3 devices:attach -d foodevice -n assets.example.com
oauth3 devices:attach -d foodevice -n cloud.example.com
oauth3 devices:attach -d foodevice -n api.cloud.example.com
```
Example `/etc/goldilocks/goldilocks.yml`:
@ -171,7 +194,7 @@ Resetting the Initialization
Once you run the app the initialization files will appear in these locations
```
/srv/walnut/var/walnut+config@daplie.com.sqlite3
/srv/walnut/var/walnut+config@oauth3.org.sqlite3
/srv/walnut/config/<domain.tld>/config.json
```
@ -267,7 +290,7 @@ The permissions:
```
/srv/walnut/var/
└── sites
└── daplie.me
└── example.com
'''
seed@example.com # refers to /srv/walnut/packages/pages/seed@example.com
'''

View File

@ -149,9 +149,10 @@ module.exports.create = function () {
process.on('unhandledRejection', function (err) {
// this should always throw
// (it means somewhere we're not using bluebird by accident)
console.error('[caught] [unhandledRejection]');
console.error(Object.keys(err));
console.error(err);
console.error('[caught unhandledRejection]:', err.message || '');
Object.keys(err).forEach(function (key) {
console.log('\t'+key+': '+err[key]);
});
console.error(err.stack);
});
process.on('rejectionHandled', function (msg) {

View File

@ -19,15 +19,15 @@ StartLimitBurst=3
# User and group the process will run as
# (www-data is the de facto standard on most systems)
User=www-data
Group=www-data
User=MY_USER
Group=MY_GROUP
# If we need to pass environment variables in the future
; Environment=GOLDILOCKS_PATH=/opt/walnut
# Set a sane working directory, sane flags, and specify how to reload the config file
WorkingDirectory=/srv/www
ExecStart=/usr/local/bin/node /srv/walnut/core/bin/walnut.js --config=/etc/walnut/walnut.yml
WorkingDirectory=/opt/walnut
ExecStart=/opt/walnut/bin/node /opt/walnut/core/bin/walnut.js --config=/etc/walnut/walnut.yml
ExecReload=/bin/kill -USR1 $MAINPID
# Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings.
@ -46,7 +46,7 @@ ProtectSystem=full
# … except TLS/SSL, ACME, and Let's Encrypt certificates
# and /var/log/, because we want a place where logs can go.
# This merely retains r/w access rights, it does not add any new. Must still be writable on the host!
ReadWriteDirectories=/etc/walnut /var/log/walnut /var/walnut /opt/walnut /srv/www
ReadWriteDirectories=/etc/walnut /var/log/walnut /var/walnut /opt/walnut /srv/walnut
# Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories
; ReadWritePaths=/etc/walnut /var/log/walnut

View File

@ -1,12 +1,5 @@
# /etc/tmpfiles.d/walnut.conf
# /etc/tmpfiles.d/goldilocks.conf
# See https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html
# Type Path Mode UID GID Age Argument
d /etc/walnut 0755 www-data www-data - -
d /etc/ssl/walnut 0750 www-data www-data - -
d /srv/walnut 0775 www-data www-data - -
d /srv/www 0775 www-data www-data - -
d /opt/walnut 0775 www-data www-data - -
d /var/walnut 0775 www-data www-data - -
d /var/log/walnut 0750 www-data www-data - -
#d /run/walnut 0755 www-data www-data - -
d /run/goldilocks 0755 MY_USER MY_GROUP - -

0
dist/etc/walnut/walnut.example.yml vendored Normal file
View File

View File

@ -1,298 +0,0 @@
#!/bin/bash
set -e
set -u
# something or other about android and tmux using PREFIX
#: "${PREFIX:=''}"
MY_ROOT=""
if [ -z "${PREFIX-}" ]; then
MY_ROOT=""
else
MY_ROOT="$PREFIX"
fi
# Not every platform has or needs sudo, gotta save them O(1)s...
sudo_cmd=""
((EUID)) && [[ -z "${ANDROID_ROOT-}" ]] && sudo_cmd="sudo"
###############################
# #
# http_get #
# boilerplate for curl / wget #
# #
###############################
# See https://git.daplie.com/Daplie/daplie-snippets/blob/master/bash/http-get.sh
http_curl_opts="-fsSL"
http_wget_opts="--quiet"
http_bin=""
http_opts=""
http_out=""
detect_http_bin()
{
if type -p curl >/dev/null 2>&1; then
http_bin="curl"
http_opts="$http_curl_opts"
http_out="-o"
#curl -fsSL "$url" -o "$PREFIX/tmp/$pkg"
elif type -p wget >/dev/null 2>&1; then
http_bin="wget"
http_opts="$http_wget_opts"
http_out="-O"
#wget --quiet "$url" -O "$PREFIX/tmp/$pkg"
else
echo "Aborted, could not find curl or wget"
return 7
fi
}
http_get()
{
if [ -e "$1" ]; then
rsync -a "$1" "$2"
elif type -p curl >/dev/null 2>&1; then
$http_bin $http_curl_opts $http_out "$2" "$1"
elif type -p wget >/dev/null 2>&1; then
$http_bin $http_wget_opts $http_out "$2" "$1"
else
echo "Aborted, could not find curl or wget"
return 7
fi
}
dap_dl()
{
http_get "$1" "$2"
}
dap_dl_bash()
{
dap_url=$1
#dap_args=$2
rm -rf /tmp/dap-tmp-runner.sh
$http_bin $http_opts $http_out /tmp/dap-tmp-runner.sh "$dap_url"; bash /tmp/dap-tmp-runner.sh; rm /tmp/dap-tmp-runner.sh
}
detect_http_bin
## END HTTP_GET ##
mvdir_backward_compat()
{
old_dir=$1
new_dir=$2
# The symlink has already been set up, so no need to do anything.
if [ -L $old_dir ] && [ $(readlink $old_dir) == "$new_dir" ]; then
return 0
fi
if [ -d $old_dir ]; then
if [ $(ls $old_dir | wc -l) -gt 0 ]; then
mv ${old_dir}/* ${new_dir}/
fi
rm -r ${old_dir}
#rmdir ${old_dir}
fi
ln -snf $new_dir $old_dir
}
###################
# #
# Install service #
# #
###################
install_for_systemd()
{
echo ""
echo "Installing as systemd service"
echo ""
mkdir -p $(dirname "$my_app_dir/$my_app_systemd_service")
dap_dl "$installer_base/$my_app_systemd_service" "$my_app_dir/$my_app_systemd_service"
$sudo_cmd mv "$my_app_dir/$my_app_systemd_service" "$MY_ROOT/$my_app_systemd_service"
$sudo_cmd chown -R root:root "$MY_ROOT/$my_app_systemd_service"
$sudo_cmd chmod 644 "$MY_ROOT/$my_app_systemd_service"
mkdir -p $(dirname "$my_app_dir/$my_app_systemd_tmpfiles")
dap_dl "$installer_base/$my_app_systemd_tmpfiles" "$my_app_dir/$my_app_systemd_tmpfiles"
$sudo_cmd mv "$my_app_dir/$my_app_systemd_tmpfiles" "$MY_ROOT/$my_app_systemd_tmpfiles"
$sudo_cmd chown -R root:root "$MY_ROOT/$my_app_systemd_tmpfiles"
$sudo_cmd chmod 644 "$MY_ROOT/$my_app_systemd_tmpfiles"
$sudo_cmd systemctl stop "${my_app_name}.service" >/dev/null 2>/dev/null
$sudo_cmd systemctl daemon-reload
$sudo_cmd systemctl start "${my_app_name}.service"
$sudo_cmd systemctl enable "${my_app_name}.service"
echo "$my_app_name started with systemctl, check its status like so"
echo " $sudo_cmd systemctl status $my_app_name"
echo " $sudo_cmd journalctl -xe -u $my_app_name"
}
install_for_launchd()
{
echo ""
echo "Installing as launchd service"
echo ""
# See http://www.launchd.info/
mkdir -p $(dirname "$my_app_dir/$my_app_launchd_service")
dap_dl "$installer_base/$my_app_launchd_service" "$my_app_dir/$my_app_launchd_service"
$sudo_cmd mv "$my_app_dir/$my_app_launchd_service" "$MY_ROOT/$my_app_launchd_service"
$sudo_cmd chown root:wheel "$MY_ROOT/$my_app_launchd_service"
$sudo_cmd chmod 0644 "$MY_ROOT/$my_app_launchd_service"
$sudo_cmd launchctl unload -w "$MY_ROOT/$my_app_launchd_service" >/dev/null 2>/dev/null
$sudo_cmd launchctl load -w "$MY_ROOT/$my_app_launchd_service"
echo "$my_app_name started with launchd"
}
install_etc_config()
{
#echo "install etc config $MY_ROOT / $my_app_etc_config"
if [ ! -e "$MY_ROOT/$my_app_etc_config" ]; then
$sudo_cmd mkdir -p $(dirname "$MY_ROOT/$my_app_etc_config")
mkdir -p $(dirname "$my_app_dir/$my_app_etc_config")
dap_dl "$installer_base/$my_app_etc_config" "$my_app_dir/$my_app_etc_config"
$sudo_cmd mv "$my_app_dir/$my_app_etc_config" "$MY_ROOT/$my_app_etc_config"
fi
$sudo_cmd chown -R www-data:www-data $(dirname "$MY_ROOT/$my_app_etc_config") || true
$sudo_cmd chown -R _www:_www $(dirname "$MY_ROOT/$my_app_etc_config") || true
$sudo_cmd chmod 775 $(dirname "$MY_ROOT/$my_app_etc_config")
$sudo_cmd chmod 664 "$MY_ROOT/$my_app_etc_config"
}
install_service()
{
install_etc_config
#echo "install service"
installable=""
if [ -d "$MY_ROOT/etc/systemd/system" ]; then
install_for_systemd
installable="true"
fi
if [ -d "/Library/LaunchDaemons" ]; then
install_for_launchd
installable="true"
fi
if [ -z "$installable" ]; then
echo ""
echo "Unknown system service init type. You must install as a system service manually."
echo '(please file a bug with the output of "uname -a")'
echo ""
fi
echo ""
}
## END SERVICE_INSTALL ##
# Create dirs, set perms
create_skeleton()
{
$sudo_cmd mkdir -p /srv/www
$sudo_cmd mkdir -p /var/log/$my_app_name
$sudo_cmd mkdir -p /etc/$my_app_name
$sudo_cmd mkdir -p /var/$my_app_name
$sudo_cmd mkdir -p /srv/$my_app_name
$sudo_cmd mkdir -p /opt/$my_app_name
}
# Unistall
install_uninstaller()
{
#echo "install uninstaller"
dap_dl "https://git.daplie.com/Daplie/walnut.js/raw/master/uninstall.sh" "./walnut-uninstall"
$sudo_cmd chmod 755 "./walnut-uninstall"
$sudo_cmd chown root:root "./walnut-uninstall"
$sudo_cmd mv "./walnut-uninstall" "/usr/local/bin/uninstall-walnut"
}
# Dependencies
export NODE_PATH=/opt/walnut/lib/node_modules
export NPM_CONFIG_PREFIX=/opt/walnut
$sudo_cmd mkdir -p $NODE_PATH
$sudo_cmd chown -R $(whoami) /opt/walnut
dap_dl_bash "https://git.daplie.com/coolaj86/node-install-script/raw/master/setup-min.sh"
# Install
# npm install -g 'git+https://git@git.daplie.com/Daplie/walnut.js.git#v1'
my_app_name=walnut
my_app_pkg_name=com.daplie.walnut.web
my_app_dir=$(mktemp -d)
#installer_base="https://git.daplie.com/Daplie/walnut.js/raw/master/dist"
#installer_base="$( dirname "${BASH_SOURCE[0]}" )/dist"
installer_base="/srv/walnut/core/dist"
my_app_etc_config="etc/${my_app_name}/${my_app_name}.yml"
my_app_systemd_service="etc/systemd/system/${my_app_name}.service"
my_app_systemd_tmpfiles="etc/tmpfiles.d/${my_app_name}.conf"
my_app_launchd_service="Library/LaunchDaemons/${my_app_pkg_name}.plist"
# Install
install_my_app()
{
# This function shouldn't need to use $sudo_cmd because it is called immediately after
# /srv/walnut is chown-ed and we only mess with things in that directory.
#git clone git@git.daplie.com:Daplie/walnut.js.git
#git clone https://git.daplie.com/Daplie/walnut.js.git /srv/walnut/core
mkdir -p /srv/walnut/{core,lib,var,etc,config,node_modules}
rm -rf /srv/walnut/core/node_modules
ln -sf ../node_modules /srv/walnut/core/node_modules
mkdir -p /srv/walnut/var/sites
mkdir -p /srv/walnut/etc/org.oauth3.consumer
mkdir -p /srv/walnut/etc/org.oauth3.provider
mkdir -p /srv/walnut/etc/client-api-grants
mkdir -p /srv/walnut/packages/{rest,api,pages,services}
# backwards compat
mvdir_backward_compat /srv/walnut/packages/client-api-grants /srv/walnut/etc/client-api-grants
mvdir_backward_compat /srv/walnut/packages/sites /srv/walnut/var/sites
if [ ! -d "/srv/walnut/core/lib/walnut@daplie.com/setup" ]; then
git clone https://git.daplie.com/Daplie/walnut_launchpad.git /srv/walnut/core/lib/walnut@daplie.com/setup
fi
pushd /srv/walnut/core/lib/walnut@daplie.com/setup
if [ ! -d "./.git/" ]; then
echo "'/srv/walnut/core/lib/walnut@daplie.com/setup' exists but is not a git repository... not sure what to do here..."
fi
git checkout v1
git pull
popd
pushd /srv/walnut/core
/opt/walnut/bin/npm install
popd
}
$sudo_cmd mkdir -p /srv/walnut
$sudo_cmd chown -R $(whoami) /srv/walnut
install_my_app
create_skeleton
install_uninstaller
install_service
$sudo_cmd chown -R www-data:www-data /opt/walnut || true
$sudo_cmd chown -R _www:_www /opt/walnut || true
$sudo_cmd chown -R www-data:www-data /srv/walnut || true
$sudo_cmd chown -R _www:_www /srv/walnut || true
$sudo_cmd chmod -R ug+rwX /srv/walnut
$sudo_cmd chmod -R ug+rwX /opt/walnut
# +s sets the setuid/setgid bit, which when set on directories makes it so anything
# created inside the directory maintains the same user/group (depending on the bits
# set). Any directory created within a directory with those bits set will also have
# those bits set. When setuid or setgid bits are set on a file however it means that
# if the file is executed it will run with the permissions of the user/group no matter
# who actually runs it (see the ping executable for example).
# I'm not sure that all systems actually support the use of these bits.
find /srv/walnut -type d -exec $sudo_cmd chmod ug+s {} \; || true
find /opt/walnut -type d -exec $sudo_cmd chmod ug+s {} \; || true

View File

@ -1,100 +0,0 @@
#!/bin/bash
set -e
set -u
###############################
# #
# boilerplate for curl / wget #
# #
###############################
http_get=""
http_opts=""
http_out=""
detect_http_get()
{
if type -p curl >/dev/null 2>&1; then
http_get="curl"
http_opts="-fsSL"
http_out="-o"
#curl -fsSL "$caddy_url" -o "$PREFIX/tmp/$caddy_pkg"
elif type -p wget >/dev/null 2>&1; then
http_get="wget"
http_opts="--quiet"
http_out="-O"
#wget --quiet "$caddy_url" -O "$PREFIX/tmp/$caddy_pkg"
else
echo "Aborted, could not find curl or wget"
return 7
fi
}
dap_dl()
{
$http_get $http_opts $http_out "$2" "$1"
}
dap_dl_bash()
{
dap_url=$1
#dap_args=$2
rm -rf dap-tmp-runner.sh
$http_get $http_opts $http_out dap-tmp-runner.sh "$dap_url"; bash dap-tmp-runner.sh; rm dap-tmp-runner.sh
}
detect_http_get
###############################
# #
# actual script continues... #
# #
###############################
install_walnut()
{
sudo mkdir -p /srv/walnut/{var,etc,packages,node_modules}
# www-data exists on linux, _www exists on mac OS
sudo chown -R $(whoami):www-data /srv/walnut || sudo chown -R $(whoami):_www /srv/walnut
if [ ! -d "/srv/walnut/core/" ]; then
git clone https://git.daplie.com/Daplie/walnut.js.git /srv/walnut/core
fi
pushd /srv/walnut/core
if [ ! -d "./.git/" ]; then
echo "'/srv/walnut/core' exists but is not a git repository... not sure what to do here..."
fi
git checkout v1
git pull
popd
rm -rf /srv/walnut/core/node_modules
ln -sf ../node_modules /srv/walnut/core/node_modules
/srv/walnut/core/install-helper.sh /srv/walnut
# Now that the install is finished we need to set the owner to the user that will actually
# be running the walnut server.
sudo chown -R www-data:www-data /srv/walnut || sudo chown -R _www:_www /srv/walnut
}
# Install node
echo "v8.2.1" > /tmp/NODEJS_VER
daplie-install-node-dev
npm install -g npm@4
# Install goldilocks
daplie-install-goldilocks
# Install walnut
install_walnut
echo ""
echo "You must have some set of domain set up to properly use goldilocks+walnut:"
echo ""
echo " example.com"
echo " www.example.com"
echo " api.example.com"
echo " assets.example.com"
echo " cloud.example.com"
echo " api.cloud.example.com"
echo ""
echo "Check the WALNUT README.md for more info and how to set up /etc/goldilocks/goldilocks.yml"
echo ""

20
installer/get.sh Normal file
View File

@ -0,0 +1,20 @@
set -e
set -u
my_name=walnut
# TODO provide an option to supply my_ver and my_tmp
my_ver=master
my_tmp=$(mktemp -d)
mkdir -p $my_tmp/opt/$my_name/lib/node_modules/$my_name
git clone https://git.coolaj86.com/coolaj86/walnut.js.git $my_tmp/opt/$my_name/core
echo "Installing to $my_tmp (will be moved after install)"
pushd $my_tmp/opt/$my_name/core
git checkout $my_ver
source ./installer/install.sh
popd
echo "Installation successful, now cleaning up $my_tmp ..."
rm -rf $my_tmp
echo "Done"

48
installer/http-get.sh Normal file
View File

@ -0,0 +1,48 @@
###############################
# #
# http_get #
# boilerplate for curl / wget #
# #
###############################
# See https://git.coolaj86.com/coolaj86/snippets/blob/master/bash/http-get.sh
_h_http_get=""
_h_http_opts=""
_h_http_out=""
detect_http_get()
{
set +e
if type -p curl >/dev/null 2>&1; then
_h_http_get="curl"
_h_http_opts="-fsSL"
_h_http_out="-o"
elif type -p wget >/dev/null 2>&1; then
_h_http_get="wget"
_h_http_opts="--quiet"
_h_http_out="-O"
else
echo "Aborted, could not find curl or wget"
return 7
fi
set -e
}
http_get()
{
$_h_http_get $_h_http_opts $_h_http_out "$2" "$1"
touch "$2"
}
http_bash()
{
_http_url=$1
#dap_args=$2
rm -rf dap-tmp-runner.sh
$_h_http_get $_h_http_opts $_h_http_out dap-tmp-runner.sh "$_http_url"; bash dap-tmp-runner.sh; rm dap-tmp-runner.sh
}
detect_http_get
## END HTTP_GET ##

View File

@ -0,0 +1,17 @@
set -u
my_app_launchd_service="Library/LaunchDaemons/${my_app_pkg_name}.plist"
echo ""
echo "Installing as launchd service"
echo ""
# See http://www.launchd.info/
safe_copy_config "$my_app_dist/$my_app_launchd_service" "$my_root/$my_app_launchd_service"
$sudo_cmd chown root:wheel "$my_root/$my_app_launchd_service"
$sudo_cmd launchctl unload -w "$my_root/$my_app_launchd_service" >/dev/null 2>/dev/null
$sudo_cmd launchctl load -w "$my_root/$my_app_launchd_service"
echo "$my_app_name started with launchd"

View File

@ -0,0 +1,35 @@
set -u
my_app_systemd_service="etc/systemd/system/${my_app_name}.service"
my_app_systemd_tmpfiles="etc/tmpfiles.d/${my_app_name}.conf"
echo ""
echo "Installing as systemd service"
echo ""
sed "s/MY_USER/$my_user/g" "$my_app_dist/$my_app_systemd_service" > "$my_app_dist/$my_app_systemd_service.2"
sed "s/MY_GROUP/$my_group/g" "$my_app_dist/$my_app_systemd_service.2" > "$my_app_dist/$my_app_systemd_service"
rm "$my_app_dist/$my_app_systemd_service.2"
safe_copy_config "$my_app_dist/$my_app_systemd_service" "$my_root/$my_app_systemd_service"
sed "s/MY_USER/$my_user/g" "$my_app_dist/$my_app_systemd_tmpfiles" > "$my_app_dist/$my_app_systemd_tmpfiles.2"
sed "s/MY_GROUP/$my_group/g" "$my_app_dist/$my_app_systemd_tmpfiles.2" > "$my_app_dist/$my_app_systemd_tmpfiles"
rm "$my_app_dist/$my_app_systemd_tmpfiles.2"
safe_copy_config "$my_app_dist/$my_app_systemd_tmpfiles" "$my_root/$my_app_systemd_tmpfiles"
$sudo_cmd systemctl stop "${my_app_name}.service" >/dev/null 2>/dev/null || true
$sudo_cmd systemctl daemon-reload
$sudo_cmd systemctl start "${my_app_name}.service"
$sudo_cmd systemctl enable "${my_app_name}.service"
echo ""
echo ""
echo "Fun systemd commands to remember:"
echo " $sudo_cmd systemctl daemon-reload"
echo " $sudo_cmd systemctl restart $my_app_name.service"
echo ""
echo "$my_app_name started with systemctl, check its status like so:"
echo " $sudo_cmd systemctl status $my_app_name"
echo " $sudo_cmd journalctl -xefu $my_app_name"
echo ""
echo ""

View File

@ -0,0 +1,37 @@
safe_copy_config()
{
src=$1
dst=$2
$sudo_cmd mkdir -p $(dirname "$dst")
if [ -f "$dst" ]; then
$sudo_cmd rsync -a "$src" "$dst.latest"
# TODO edit config file with $my_user and $my_group
if [ "$(cat $dst)" == "$(cat $dst.latest)" ]; then
$sudo_cmd rm $dst.latest
else
echo "MANUAL INTERVENTION REQUIRED: check the systemd script update and manually decide what you want to do"
echo "diff $dst $dst.latest"
$sudo_cmd chown -R root:root "$dst.latest"
fi
else
$sudo_cmd rsync -a --ignore-existing "$src" "$dst"
fi
$sudo_cmd chown -R root:root "$dst"
$sudo_cmd chmod 644 "$dst"
}
installable=""
if [ -d "$my_root/etc/systemd/system" ]; then
source ./installer/install-for-systemd.sh
installable="true"
fi
if [ -d "/Library/LaunchDaemons" ]; then
source ./installer/install-for-launchd.sh
installable="true"
fi
if [ -z "$installable" ]; then
echo ""
echo "Unknown system service init type. You must install as a system service manually."
echo '(please file a bug with the output of "uname -a")'
echo ""
fi

195
installer/install.sh Normal file
View File

@ -0,0 +1,195 @@
#!/bin/bash
set -e
set -u
### IMPORTANT ###
### VERSION ###
my_name=walnut
my_app_pkg_name=org.oauth3.walnut.web
my_app_ver="v1.2"
my_azp_oauth3_ver="v1.2"
# is the old version still needed in launchpad?
#my_azp_oauth3_ver="v1.1.3"
export NODE_VERSION="v8.9.0"
if [ -z "${my_tmp-}" ]; then
my_tmp="$(mktemp -d)"
mkdir -p $my_tmp/opt/$my_name/core
echo "Installing to $my_tmp (will be moved after install)"
git clone ./ $my_tmp/opt/$my_name/core
pushd $my_tmp/opt/$my_name/core
fi
#################
### IMPORTANT ###
### VERSION ###
#my_app_ver="v1.1"
my_app_ver="v1.2"
my_launchpad_ver="v1.2"
my_iss_oauth3_rest_ver="v1.2.0"
my_iss_oauth3_pages_ver="v1.2.1"
my_www_ppl_ver=v1.0.15
export NODE_VERSION="v8.9.0"
#################
export NODE_PATH=$my_tmp/opt/$my_name/lib/node_modules
export PATH=$my_tmp/opt/$my_name/bin/:$PATH
export NPM_CONFIG_PREFIX=$my_tmp/opt/$my_name
my_npm="$NPM_CONFIG_PREFIX/bin/npm"
#################
# TODO un-hardcode core at al
#my_app_dist=$my_tmp/opt/$my_name/lib/node_modules/$my_name/dist
my_app_dist=$my_tmp/opt/$my_name/core/dist
installer_base="https://git.coolaj86.com/coolaj86/goldilocks.js/raw/$my_app_ver"
# Backwards compat
# some scripts still use the old names
my_app_dir=$my_tmp
my_app_name=$my_name
git checkout $my_app_ver
mkdir -p $my_tmp/{etc,opt,srv,var}/$my_name
mkdir -p "$my_tmp/var/log/$my_name"
mkdir -p "$my_tmp/opt/$my_name"/{bin,config,core,etc,lib,node_modules,var}
ln -s ../core/bin/$my_name.js $my_tmp/opt/$my_name/bin/$my_name
ln -s ../core/bin/$my_name.js $my_tmp/opt/$my_name/bin/$my_name.js
#ln -s ../lib/node_modules/$my_name/bin/$my_name.js $my_tmp/opt/$my_name/bin/$my_name
#ln -s ../lib/node_modules/$my_name/bin/$my_name.js $my_tmp/opt/$my_name/bin/$my_name.js
mkdir -p "$my_tmp/opt/$my_name"/packages/{api,pages,rest,services}
mkdir -p "$my_tmp/opt/$my_name"/etc/client-api-grants
# TODO move packages and sites to /srv, grants to /etc
ln -s ../etc/client-api-grants "$my_tmp/opt/$my_name"/packages/client-api-grants
mkdir -p "$my_tmp/opt/$my_name"/var/sites
ln -s ../var/sites "$my_tmp/opt/$my_name"/packages/sites
mkdir -p "$my_tmp/etc/$my_name"
chmod 775 "$my_tmp/etc/$my_name"
cat "$my_app_dist/etc/$my_name/$my_name.example.yml" > "$my_tmp/etc/$my_name/$my_name.example.yml"
chmod 664 "$my_tmp/etc/$my_name/$my_name.example.yml"
mkdir -p $my_tmp/var/log/$my_name
#
# Helpers
#
source ./installer/sudo-cmd.sh
source ./installer/http-get.sh
#
# Dependencies
#
echo $NODE_VERSION > /tmp/NODEJS_VER
# This will read the NODE_* and PATH variables set previously, as well as /tmp/NODEJS_VER
http_bash "https://git.coolaj86.com/coolaj86/node-installer.sh/raw/v1.1/install.sh"
$my_npm install -g npm@4
$my_npm install -g bower
touch $my_tmp/opt/$my_name/.bowerrc
echo '{ "allow_root": true }' > $my_tmp/opt/$my_name/.bowerrc
#pushd $my_tmp/opt/$my_name/lib/node_modules/$my_name
pushd $my_tmp/opt/$my_name/core
mkdir -p ../node_modules
ln -s ../node_modules node_modules
$my_npm install
popd
git clone https://git.coolaj86.com/coolaj86/walnut_launchpad.html.git $my_tmp/opt/$my_name/core/lib/walnut@oauth3.org/setup
pushd $my_tmp/opt/$my_name/core/lib/walnut@oauth3.org/setup
git pull
git checkout $my_launchpad_ver
git clone https://git.oauth3.org/OAuth3/oauth3.js.git ./assets/oauth3.org
pushd assets/oauth3.org
git checkout $my_azp_oauth3_ver
popd
popd
pushd $my_tmp/opt/$my_name/packages
git clone https://git.oauth3.org/OAuth3/issuer.rest.walnut.js.git rest/issuer@oauth3.org
pushd rest/issuer@oauth3.org/
git checkout $my_iss_oauth3_rest_ver
$my_npm install
popd
git clone https://git.oauth3.org/OAuth3/issuer.html.git pages/issuer@oauth3.org
pushd pages/issuer@oauth3.org
git checkout $my_iss_oauth3_pages_ver
bash ./install.sh
pushd ./assets/oauth3.org
git checkout $my_azp_oauth3_ver
popd
popd
git clone https://git.coolaj86.com/coolaj86/walnut_rest_www_oauth3.org.js.git rest/www@oauth3.org
pushd rest/www@oauth3.org
git checkout $my_www_ppl_ver
$my_npm install
popd
popd
#
# System Service
#
source ./installer/my-root.sh
echo "Pre-installation to $my_tmp complete, now installing to $my_root/ ..."
set +e
if type -p tree >/dev/null 2>/dev/null; then
#tree -I "node_modules|include|share" $my_tmp
tree -L 6 -I "include|share|npm" $my_tmp
else
ls $my_tmp
fi
set -e
source ./installer/my-user-my-group.sh
echo "User $my_user Group $my_group"
$sudo_cmd chown -R $my_user:$my_group $my_tmp
$sudo_cmd chown root:root $my_tmp/*
$sudo_cmd chown root:root $my_tmp
$sudo_cmd chmod 0755 $my_tmp
$sudo_cmd rsync -a --ignore-existing $my_tmp/ $my_root/
$sudo_cmd rsync -a --ignore-existing $my_app_dist/etc/$my_name/$my_name.yml $my_root/etc/$my_name/$my_name.yml
source ./installer/install-system-service.sh
# Change to admin perms
$sudo_cmd chown -R $my_user:$my_group $my_root/opt/$my_name
$sudo_cmd chown -R $my_user:$my_group $my_root/var/www $my_root/srv/www
# make sure the files are all read/write for the owner and group, and then set
# the setuid and setgid bits so that any files/directories created inside these
# directories have the same owner and group.
$sudo_cmd chmod -R ug+rwX $my_root/opt/$my_name
find $my_root/opt/$my_name -type d -exec $sudo_cmd chmod ug+s {} \;
echo ""
echo "You must have some set of domain set up to properly use goldilocks+walnut:"
echo ""
echo " example.com"
echo " www.example.com"
echo " api.example.com"
echo " assets.example.com"
echo " cloud.example.com"
echo " api.cloud.example.com"
echo ""
echo "Check the WALNUT README.md for more info and how to set up /etc/goldilocks/goldilocks.yml"
echo ""
echo "Unistall: rm -rf /srv/walnut/ /var/walnut/ /etc/walnut/ /opt/walnut/ /var/log/walnut/ /etc/systemd/system/walnut.service /etc/tmpfiles.d/walnut.conf"
rm -rf $my_tmp

8
installer/my-root.sh Normal file
View File

@ -0,0 +1,8 @@
# something or other about android and tmux using PREFIX
#: "${PREFIX:=''}"
my_root=""
if [ -z "${PREFIX-}" ]; then
my_root=""
else
my_root="$PREFIX"
fi

View File

@ -0,0 +1,19 @@
if type -p adduser >/dev/null 2>/dev/null; then
if [ -z "$(cat $my_root/etc/passwd | grep $my_app_name)" ]; then
$sudo_cmd adduser --home $my_root/opt/$my_app_name --gecos '' --disabled-password $my_app_name
fi
my_user=$my_app_name
my_group=$my_app_name
elif [ -n "$(cat /etc/passwd | grep www-data:)" ]; then
# Linux (Ubuntu)
my_user=www-data
my_group=www-data
elif [ -n "$(cat /etc/passwd | grep _www:)" ]; then
# Mac
my_user=_www
my_group=_www
else
# Unsure
my_user=$(whoami)
my_group=$(id -g -n)
fi

7
installer/sudo-cmd.sh Normal file
View File

@ -0,0 +1,7 @@
# Not every platform has or needs sudo, gotta save them O(1)s...
sudo_cmd=""
set +e
if type -p sudo >/dev/null 2>/dev/null; then
((EUID)) && [[ -z "${ANDROID_ROOT-}" ]] && sudo_cmd="sudo"
fi
set -e

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,21 @@
'use strict';
module.exports.rejectableRequest = function rejectableRequest(req, res, promise, msg) {
function rejectableRequest(req, res, promise, msg) {
return promise.error(function (err) {
res.error(err);
}).catch(function (err) {
console.error('[ERROR] \'' + msg + '\'');
console.error(err.message);
console.error(err.stack);
// The stack contains the message as well, so no need to log the message when we log the stack
console.error(err.stack || err.message || JSON.stringify(err));
res.error(err);
});
};
}
module.exports.rejectableRequest = rejectableRequest;
module.exports.promisableRequest =
module.exports.promiseRequest = function promiseRequest(req, res, promise, msg) {
return promise.then(function (result) {
promise = promise.then(function (result) {
if (result._cache) {
res.setHeader('Cache-Control', 'public, max-age=' + (result._cache / 1000));
res.setHeader('Expires', new Date(Date.now() + result._cache).toUTCString());
@ -26,13 +27,7 @@ module.exports.promiseRequest = function promiseRequest(req, res, promise, msg)
result = result._value;
}
res.send(result);
}).error(function (err) {
res.error(err);
}).catch(function (err) {
console.error('[ERROR] \'' + msg + '\'');
console.error(err.message);
console.error(err.stack);
res.error(err);
});
return rejectableRequest(req, res, promise, msg);
};

View File

@ -1,6 +1,6 @@
'use strict';
module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi) {
module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi, errorIfAssets) {
var PromiseA = require('bluebird');
var path = require('path');
var fs = PromiseA.promisifyAll(require('fs'));
@ -293,10 +293,27 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi
// TODO handle assets.example.com/sub/assets/com.example.xyz/
app.use('/api', require('connect-send-error').error());
app.use('/assets', require('connect-send-error').error());
app.use('/', function (req, res, next) {
// If this doesn't look like an API we can move along
if (!/\/api(\/|$)/.test(req.url)) {
// /^api\./.test(req.hostname) &&
// If this doesn't look like an API or assets we can move along
/*
console.log('.');
console.log('[main.js] req.url, req.hostname');
console.log(req.url);
console.log(req.hostname);
console.log('.');
*/
if (!/\/(api|assets)(\/|$)/.test(req.url)) {
//console.log('[main.js] api|assets');
next();
return;
}
// keep https://assets.example.com/assets but skip https://example.com/assets
if (/\/assets(\/|$)/.test(req.url) && !/(^|\.)(api|assets)(\.)/.test(req.hostname) && !/^[0-9\.]+$/.test(req.hostname)) {
//console.log('[main.js] skip');
next();
return;
}
@ -325,6 +342,7 @@ module.exports.create = function (app, xconfx, apiFactories, apiDeps, errorIfApi
return;
});
app.use('/', errorIfApi);
app.use('/', errorIfAssets);
app.use('/', serveStatic);
app.use('/', serveApps);

306
lib/oauth3.js Normal file
View File

@ -0,0 +1,306 @@
'use strict';
var PromiseA = require('bluebird');
function generateRescope(req, Models, decoded, fullPpid, ppid) {
return function (/*sub*/) {
// TODO: this function is supposed to convert PPIDs of different parties to some account
// ID that allows application to keep track of permisions and what-not.
console.log('[rescope] Attempting ', fullPpid);
return Models.IssuerOauth3OrgGrants.find({ azpSub: fullPpid }).then(function (results) {
if (results[0]) {
console.log('[rescope] lukcy duck: got it on the 1st try');
return PromiseA.resolve(results);
}
// XXX BUG XXX
// should be able to distinguish between own ids and 3rd party via @whatever.com
return Models.IssuerOauth3OrgGrants.find({ azpSub: ppid });
}).then(function (results) {
var result = results[0];
if (!result || !result.sub || !decoded.iss) {
// XXX BUG XXX TODO swap this external ppid for an internal (and ask user to link with existing profile)
//req.oauth3.accountIdx = fullPpid;
throw new Error("internal / external ID swapping not yet implemented. TODO: "
+ "No profile found with that credential. Would you like to create a new profile or link to an existing profile?");
}
// XXX BUG XXX need to pass own url in to use as issuer for own tokens
req.oauth3.accountIdx = result.sub + '@' + decoded.iss;
console.log('[rescope] result:');
console.log(results);
console.log(req.oauth3.accountIdx);
return PromiseA.resolve(req.oauth3.accountIdx);
});
};
}
function extractAccessToken(req) {
var token = null;
var parts;
var scheme;
var credentials;
if (req.headers && req.headers.authorization) {
// Works for all of Authorization: Bearer {{ token }}, Token {{ token }}, JWT {{ token }}
parts = req.headers.authorization.split(' ');
if (parts.length !== 2) {
return PromiseA.reject(new Error("malformed Authorization header"));
}
scheme = parts[0];
credentials = parts[1];
if (-1 !== ['token', 'bearer'].indexOf(scheme.toLowerCase())) {
token = credentials;
}
}
if (req.body && req.body.access_token) {
if (token) { PromiseA.reject(new Error("token exists in header and body")); }
token = req.body.access_token;
}
// TODO disallow query with req.method === 'GET'
// NOTE: the case of DDNS on routers requires a GET and access_token
// (cookies should be used for protected static assets)
if (req.query && req.query.access_token) {
if (token) { PromiseA.reject(new Error("token already exists in either header or body and also in query")); }
token = req.query.access_token;
}
/*
err = new Error(challenge());
err.code = 'E_BEARER_REALM';
if (!token) { return PromiseA.reject(err); }
*/
return PromiseA.resolve(token);
}
function verifyToken(token) {
var jwt = require('jsonwebtoken');
var decoded;
if (!token) {
return PromiseA.reject({
message: 'no token provided'
, code: 'E_NO_TOKEN'
, url: 'https://oauth3.org/docs/errors#E_NO_TOKEN'
});
}
try {
decoded = jwt.decode(token, {complete: true});
} catch (e) {}
if (!decoded) {
return PromiseA.reject({
message: 'provided token not a JSON Web Token'
, code: 'E_NOT_JWT'
, url: 'https://oauth3.org/docs/errors#E_NOT_JWT'
});
}
var sub = decoded.payload.sub || decoded.payload.ppid || decoded.payload.appScopedId;
if (!sub) {
return PromiseA.reject({
message: 'token missing sub'
, code: 'E_MISSING_SUB'
, url: 'https://oauth3.org/docs/errors#E_MISSING_SUB'
});
}
var kid = decoded.header.kid || decoded.payload.kid;
if (!kid) {
return PromiseA.reject({
message: 'token missing kid'
, code: 'E_MISSING_KID'
, url: 'https://oauth3.org/docs/errors#E_MISSING_KID'
});
}
if (!decoded.payload.iss) {
return PromiseA.reject({
message: 'token missing iss'
, code: 'E_MISSING_ISS'
, url: 'https://oauth3.org/docs/errors#E_MISSING_ISS'
});
}
var OAUTH3 = require('oauth3.js');
OAUTH3._hooks = require('oauth3.js/oauth3.node.storage.js');
return OAUTH3.discover(decoded.payload.iss).then(function (directives) {
var args = (directives || {}).retrieve_jwk;
if (typeof args === 'string') {
args = { url: args, method: 'GET' };
}
if (typeof (args || {}).url !== 'string') {
return PromiseA.reject({
message: 'token issuer does not support retrieving JWKs'
, code: 'E_INVALID_ISS'
, url: 'https://oauth3.org/docs/errors#E_INVALID_ISS'
});
}
var params = {
sub: sub
, kid: kid
};
var url = args.url;
var body;
Object.keys(params).forEach(function (key) {
if (url.indexOf(':'+key) !== -1) {
url = url.replace(':'+key, params[key]);
delete params[key];
}
});
if (Object.keys(params).length > 0) {
if ('GET' === (args.method || 'GET').toUpperCase()) {
url += '?' + OAUTH3.query.stringify(params);
} else {
body = params;
}
}
return OAUTH3.request({
url: OAUTH3.url.resolve(directives.api, url)
, method: args.method
, data: body
}).catch(function (err) {
return PromiseA.reject({
message: 'failed to retrieve public key from token issuer'
, code: 'E_NO_PUB_KEY'
, url: 'https://oauth3.org/docs/errors#E_NO_PUB_KEY'
, subErr: err.toString()
});
});
}, function (err) {
return PromiseA.reject({
message: 'token issuer is not a valid OAuth3 provider'
, code: 'E_INVALID_ISS'
, url: 'https://oauth3.org/docs/errors#E_INVALID_ISS'
, subErr: err.toString()
});
}).then(function (res) {
if (res.data.error) {
return PromiseA.reject(res.data.error);
}
var opts = {};
if (Array.isArray(res.data.alg)) {
opts.algorithms = res.data.alg;
} else if (typeof res.data.alg === 'string') {
opts.algorithms = [res.data.alg];
}
try {
return jwt.verify(token, require('jwk-to-pem')(res.data), opts);
} catch (err) {
return PromiseA.reject({
message: 'token verification failed'
, code: 'E_INVALID_TOKEN'
, url: 'https://oauth3.org/docs/errors#E_INVALID_TOKEN'
, subErr: err.toString()
});
}
});
}
function deepFreeze(obj) {
Object.keys(obj).forEach(function (key) {
if (obj[key] && typeof obj[key] === 'object') {
deepFreeze(obj[key]);
}
});
Object.freeze(obj);
}
function cookieOauth3(Models, req, res, next) {
req.oauth3 = {};
var token = req.cookies.jwt;
req.oauth3.encodedToken = token;
req.oauth3.verifyAsync = function (jwt) {
return verifyToken(jwt || token);
};
return verifyToken(token).then(function (decoded) {
req.oauth3.token = decoded;
if (!decoded) {
return null;
}
var ppid = decoded.sub || decoded.ppid || decoded.appScopedId;
req.oauth3.ppid = ppid;
req.oauth3.accountIdx = ppid+'@'+decoded.iss;
var hash = require('crypto').createHash('sha256').update(req.oauth3.accountIdx).digest('base64');
hash = hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+/g, '');
req.oauth3.accountHash = hash;
req.oauth3.rescope = generateRescope(req, Models, decoded, fullPpid, ppid);
}).then(function () {
deepFreeze(req.oauth3);
//Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
next();
}, function (err) {
if ('E_NO_TOKEN' === err.code) {
next();
return;
}
console.error('[walnut] cookie lib/oauth3 error:');
console.error(err);
res.send(err);
});
}
function attachOauth3(Models, req, res, next) {
req.oauth3 = {};
extractAccessToken(req).then(function (token) {
req.oauth3.encodedToken = token;
req.oauth3.verifyAsync = function (jwt) {
return verifyToken(jwt || token);
};
if (!token) {
return null;
}
return verifyToken(token);
}).then(function (decoded) {
req.oauth3.token = decoded;
if (!decoded) {
return null;
}
var ppid = decoded.sub || decoded.ppid || decoded.appScopedId;
var fullPpid = ppid+'@'+decoded.iss;
req.oauth3.ppid = ppid;
// TODO we can anonymize the relationship between our user as the other service's user
// in our own database by hashing the remote service's ppid and using that as the lookup
var hash = require('crypto').createHash('sha256').update(fullPpid).digest('base64');
hash = hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+/g, '');
req.oauth3.accountHash = hash;
req.oauth3.rescope = generateRescope(req, Models, decoded, fullPpid, ppid);
console.log('############### assigned req.oauth3:');
console.log(req.oauth3);
}).then(function () {
//deepFreeze(req.oauth3);
//Object.defineProperty(req, 'oauth3', {configurable: false, writable: false});
next();
}, function (err) {
console.error('[walnut] JWT lib/oauth3 error:');
console.error(err);
res.send(err);
});
}
module.exports.attachOauth3 = attachOauth3;
module.exports.cookieOauth3 = cookieOauth3;
module.exports.verifyToken = verifyToken;

View File

@ -55,19 +55,7 @@ function getApi(conf, pkgConf, pkgDeps, packagedApi) {
packagedApi._api = require('express-lazy')();
packagedApi._api_app = myApp;
//require('./oauth3-auth').inject(conf, packagedApi._api, pkgConf, pkgDeps);
pkgDeps.getOauth3Controllers =
packagedApi._getOauth3Controllers = require('oauthcommon/example-oauthmodels').create(conf).getControllers;
require('oauthcommon').inject(packagedApi._getOauth3Controllers, packagedApi._api, pkgConf, pkgDeps);
// DEBUG
//
/*
packagedApi._api.use('/', function (req, res, next) {
console.log('[DEBUG pkgApiApp]', req.method, req.hostname, req.url);
next();
});
//*/
packagedApi._api.use('/', require('./oauth3').attachOauth3);
// TODO fix backwards compat

View File

@ -150,6 +150,21 @@ module.exports.create = function (webserver, xconfx, state) {
models: models
// TODO don't let packages use this directly
, Promise: PromiseA
, dns: PromiseA.promisifyAll(require('dns'))
, crypto: PromiseA.promisifyAll(require('crypto'))
, fs: PromiseA.promisifyAll(require('fs'))
, path: require('path')
, validate: {
isEmail: function (email) {
return /@/.test(email) && !/\s+/.test(email);
}
, email: function (email) {
if (apiDeps.validate.isEmail(email)) {
return null;
}
return new Error('invalid email address');
}
}
};
var apiFactories = {
memstoreFactory: { create: scopeMemstore }
@ -180,7 +195,7 @@ module.exports.create = function (webserver, xconfx, state) {
function setupMain() {
if (xconfx.debug) { console.log('[main] setup'); }
mainApp = express();
require('./main').create(mainApp, xconfx, apiFactories, apiDeps, errorIfApi).then(function () {
require('./main').create(mainApp, xconfx, apiFactories, apiDeps, errorIfApi, errorIfAssets).then(function () {
if (xconfx.debug) { console.log('[main] ready'); }
// TODO process.send({});
});
@ -225,6 +240,24 @@ module.exports.create = function (webserver, xconfx, state) {
next();
}
function errorIfNotAssets(req, res, next) {
var hostname = req.hostname || req.headers.host;
if (!/^assets\.[a-z0-9\-]+/.test(hostname)) {
res.send({ error:
{ message: "['" + hostname + req.url + "'] protected asset access is restricted to proper 'asset'-prefixed lowercase subdomains."
+ " The HTTP 'Host' header must exist and must begin with 'assets.' as in 'assets.example.com'."
+ " For development you may test with assets.localhost.daplie.me (or any domain by modifying your /etc/hosts)"
, code: 'E_NOT_API'
, _hostname: hostname
}
});
return;
}
next();
}
function errorIfApi(req, res, next) {
if (!/^api\./.test(req.headers.host)) {
next();
@ -240,7 +273,25 @@ module.exports.create = function (webserver, xconfx, state) {
return;
}
res.send({ error: { code: 'E_NO_IMPL', message: "not implemented" } });
res.send({ error: { code: 'E_NO_IMPL', message: "API not implemented" } });
}
function errorIfAssets(req, res, next) {
if (!/^assets\./.test(req.headers.host)) {
next();
return;
}
// has api. hostname prefix
// doesn't have /api url prefix
if (!/^\/assets\//.test(req.url)) {
console.log('[walnut/worker assets] req.url', req.url);
res.send({ error: { message: "missing /assets/ url prefix" } });
return;
}
res.send({ error: { code: 'E_NO_IMPL', message: "assets handler not implemented" } });
}
app.disable('x-powered-by');
@ -258,8 +309,11 @@ module.exports.create = function (webserver, xconfx, state) {
}));
app.use('/api', recase);
var cookieParser = require('cookie-parser'); // signing is done in JWT
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
app.use('/api', errorIfNotApi);
app.use('/assets', /*errorIfNotAssets,*/ cookieParser()); // serializer { path: '/assets', httpOnly: true, sameSite: true/*, domain: assets.example.com*/ }
app.use('/', function (req, res) {
if (!(req.encrypted || req.secure)) {
// did not come from https

View File

@ -1,6 +1,6 @@
{
"name": "walnut",
"version": "0.1.0",
"version": "1.2.5",
"description": "zero-config home cloud server",
"main": "walnut.js",
"scripts": {
@ -8,7 +8,7 @@
},
"repository": {
"type": "git",
"url": "https://github.com/Daplie/walnut.git"
"url": "https://git.coolaj86.com/coolaj86/walnut.js.git"
},
"bin": {
"walnut": "./bin/walnut.js"
@ -33,38 +33,48 @@
"private",
"public"
],
"author": "AJ ONeal <aj@daplie.com> (https://daplie.com)",
"license": "Apache2",
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com)",
"license": "(MIT or Apache2)",
"bugs": {
"url": "https://github.com/Daplie/walnut/issues"
"url": "https://git.coolaj86.com/coolaj86/walnut.js/issues"
},
"homepage": "https://github.com/Daplie/walnut",
"homepage": "https://git.coolaj86.com/coolaj86/walnut.js",
"dependencies": {
"bluebird": "3.x",
"body-parser": "1.x",
"cluster-store": "git+https://git.daplie.com/Daplie/cluster-store.git#v2",
"cluster-store": "^2.0.8",
"connect": "3.x",
"connect-cors": "0.5.x",
"connect-recase": "^1.0.2",
"connect-send-error": "1.x",
"cookie-parser": "^1.4.3",
"escape-html": "^1.0.2",
"escape-string-regexp": "1.x",
"express": "4.x",
"express-lazy": "^1.1.1",
"express-session": "^1.11.3",
"jsonwebtoken": "^7.4.1",
"jwk-to-pem": "^1.2.6",
"mailchimp-api-v3": "^1.7.0",
"mandrill-api": "^1.0.45",
"masterquest-sqlite3": "git+https://git.daplie.com/node/masterquest-sqlite3.git",
"masterquest-sqlite3": "^1.1.1",
"mkdirp": "^0.5.1",
"multiparty": "^4.1.3",
"nodemailer": "^1.4.0",
"nodemailer-mailgun-transport": "1.x",
"oauthcommon": "git+https://git.daplie.com/node/oauthcommon.git",
"oauth3.js": "git+https://git.oauth3.org/OAuth3/oauth3.js.git#v1.2",
"recase": "^1.0.4",
"request": "^2.81.0",
"scmp": "^2.0.0",
"serve-static": "1.x",
"sqlite3-cluster": "git+https://git.daplie.com/coolaj86/sqlite3-cluster.git#v2",
"sqlite3-cluster": "^2.1.2",
"stripe": "^4.22.0",
"twilio": "1.x",
"ursa": "^0.9.1"
"twilio": "1.x"
},
"gitDependencies": {
"cluster-store": "git+https://git.coolaj86.com/coolaj86/cluster-store.git#v2",
"masterquest-sqlite3": "git+https://git.coolaj86.com/coolaj86/masterquest-sqlite3.git",
"oauth3.js": "git+https://git.oauth3.org/OAuth3/oauth3.js.git#v1.2",
"sqlite3-cluster": "git+https://git.coolaj86.com/coolaj86/sqlite3-cluster.git#v2"
}
}