├── .gitignore ├── docroot └── style.css ├── Dockerfile ├── deploy ├── linky-tests.el ├── README.creole ├── linky.el └── linky-client.el /.gitignore: -------------------------------------------------------------------------------- 1 | /.deploy 2 | -------------------------------------------------------------------------------- /docroot/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | margin: 1em 5em 1em 5em; 4 | } 5 | 6 | ul { 7 | list-style-type: none; 8 | } 9 | 10 | form { 11 | padding-top: 1em; 12 | border-top: thin solid; 13 | line-height: .3em; 14 | } 15 | 16 | form legend { 17 | padding-bottom: 1em; 18 | } 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nicferrier/elnode-and-nodejs 2 | MAINTAINER nic@ferrier.me.uk 3 | USER emacs 4 | WORKDIR /home/emacs 5 | RUN git clone https://github.com/nicferrier/elnode-linky.git 6 | WORKDIR /home/emacs/elnode-linky 7 | # RUN npm install . ### don't need node yet! 8 | EXPOSE 8005 9 | VOLUME /home/emacs/elnode-linky/db 10 | VOLUME /home/emacs/.emacs.d 11 | ENV ETAG 20140816213427254185764 12 | CMD /usr/local/emacs/bin/emacs -daemon -l linky.el ; tail -f /dev/null 13 | -------------------------------------------------------------------------------- /deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Docker deploy script generated by deploy-make 3 | 4 | [ -f ./.deploy-test ] && source ./.deploy-test 5 | [ -f ./.deploy ] || curl https://raw.githubusercontent.com/nicferrier/docker-shell-deploy/master/deploy-helpers -o ./.deploy || { echo "can't http the deployscript" ; exit 1; } 6 | . ./.deploy 7 | dockerImage=nicferrier/elnode-linky 8 | dockerExPort=8005 9 | nginxConfig=/etc/nginx/sites-enabled/linky 10 | hostName=po5.ferrier.me.uk 11 | dockerVolumes=;/home/nferrier/linky/db:/home/emacs/elnode-linky/db 12 | deploy ${1:-"deploy"} nicferrier/elnode-linky 8005 /etc/nginx/sites-enabled/linky.conf po5.ferrier.me.uk /home/nferrier/linky/db:/home/emacs/elnode-linky/db 13 | -------------------------------------------------------------------------------- /linky-tests.el: -------------------------------------------------------------------------------- 1 | ;;; linky-tests.el --- tests for linky 2 | 3 | 4 | (ert-deftest linky/store->html () 5 | (should 6 | (string-match-p 7 | "ecom" 8 | (let ((linky/store (make-hash-table :test 'equal))) 9 | (puthash "nic" 10 | '(("http://172.30.1.11:8018" "ecom") 11 | ("http://172.30.1.11:8015" "gnudoc")) 12 | linky/store) 13 | (linky/store->html "nic"))))) 14 | 15 | (ert-deftest linky/store-add () 16 | (should 17 | (string-match-p 18 | "skinny" 19 | (let ((linky/store (make-hash-table :test 'equal))) 20 | (puthash "nic" 21 | '(("http://172.30.1.11:8018" "ecom") 22 | ("http://172.30.1.11:8015" "gnudoc")) 23 | linky/store) 24 | (linky/store-add "nic" "http://172.30.1.8090" "skinny") 25 | (linky/store->html "nic"))))) 26 | 27 | (provide 'linky-tests) 28 | 29 | ;;; linky-tests.el ends here 30 | -------------------------------------------------------------------------------- /README.creole: -------------------------------------------------------------------------------- 1 | = linky = 2 | 3 | A very small link app for exposing links. 4 | 5 | == what? == 6 | 7 | You're developing a webapp locally, on your laptop, perhaps with 8 | Elnode but it could be anything. 9 | 10 | You want to test a media query intended to make the site work better 11 | on a mobile. But your mobile doesn't know what your laptop's address 12 | is. 13 | 14 | Now you're stuck. 15 | 16 | Linky is a stupid little link store. You run it on the Internet at a 17 | known address, say: http://linky.elnode.org 18 | 19 | Then you can post a link to your app running on your laptop's local IP 20 | address and port. 21 | 22 | Then you can browse to the same linky site on your mobile and find the 23 | link. 24 | 25 | == more details? == 26 | 27 | Linky has a simple basic authentication and registration system, so 28 | your links are separate from other people's links. 29 | 30 | It's not intended to be secure though, presumably only people on your 31 | LAN can see the app that is linked to on linky. 32 | 33 | 34 | === registering === 35 | 36 | You can register with a simple HTTP call: 37 | 38 | {{{ 39 | curl -d "username=tony&password=secret" http://linky.elnode.org/register/ 40 | }}} 41 | 42 | this will return a redirect to the login page. 43 | 44 | === login === 45 | 46 | Login is just form/cookie auth: 47 | 48 | {{{ 49 | curl -c ~/.cookies -d "username=tony&password=secret" http://linky.elnode.org/login/ 50 | }}} 51 | 52 | Will login and store the cookie in {{{~/.cookies}}}. You can then 53 | issue further curl's: 54 | 55 | {{{ 56 | curl -b ~/.cookies http://linky.elnode.org/ 57 | }}} 58 | 59 | 60 | === POSTing a link === 61 | 62 | POSTing a link is easy too: 63 | 64 | {{{ 65 | curl -b ~/.cookies \ 66 | -d "a=http://10.1.1.203:8066&n=a+little+app" \ 67 | http://linky.elnode.org 68 | }}} 69 | 70 | Would return a redirect to {{{/}}} 71 | 72 | 73 | === using with elnode === 74 | 75 | If you use elnode there's linky-client included here. It provides an 76 | M-x command for POSTing a link to a running elnode server to linky. 77 | 78 | linky-client needs the {{{ip}}} command and GNU sed. 79 | 80 | == other options? == 81 | 82 | If you want a more sophisticated solution check 83 | out [[http://localtunnel.me/]] which is a reverse proxy to a well 84 | known site on the Internet. 85 | 86 | The trouble with this solution is that your mobile requires a 87 | connection to the Internet for the whole session, instead of just the 88 | initial discovery. 89 | 90 | Another solution to this is to use an authentication based url 91 | shortener, something like [[http://goo.gl]]. Just paste your site into 92 | the shortener, if you're loggedin to the same account on your mobile 93 | you can retrieve it. 94 | 95 | This is basically the same as Linky. The difference is that you have 96 | to be logged in to Google and it's therefore harder to {{{curl}}} the 97 | address into it. 98 | 99 | The real solution for this is for mobile browsers to support MDNS 100 | natively, then you could set up MDNS resolution for your laptop and 101 | your mobile could just find it. There are probably security issues 102 | with that though. 103 | 104 | -------------------------------------------------------------------------------- /linky.el: -------------------------------------------------------------------------------- 1 | ;;; linky.el --- a simple link post app for help with testing on LANs -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014 Nic Ferrier 4 | 5 | ;; Author: Nic Ferrier 6 | ;; Keywords: hypermedia 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; A very simple app for solving the problem of how to find links 24 | ;; you're hosting on your local laptop. 25 | 26 | ;;; Code: 27 | 28 | (require 'elnode) 29 | (require 'db) 30 | 31 | (defvar linky/auth-db ;; this needs to be stored in a docker volume 32 | (db-make 33 | `(db-hash 34 | :filename ,(let ((file 35 | (expand-file-name "db/auth-db" default-directory))) 36 | (make-directory (file-name-directory file) t) 37 | file))) 38 | "The db where we store user information.") 39 | 40 | (elnode-defauth :linky-auth 41 | :cookie-name "linky-auth" 42 | :auth-db linky/auth-db) 43 | 44 | (defconst linky/store (make-hash-table :test 'equal) 45 | "Store, keyed by username; value is a list of links. 46 | 47 | The list of links is an alist: HREF . EXTENDED-INFO where 48 | EXTENDED-INFO is a list whose first element is an optional name 49 | for the tag. Thus the alist may have a `nil' CDR. 50 | 51 | Other information may be added to the EXTENDED-INFO.") 52 | 53 | (defconst linky/form-html "
54 | add a new address 55 |

56 |

57 | 58 |
") 59 | 60 | (defun linky/store->html (username) 61 | (let ((details (gethash username linky/store))) 62 | (format " 63 | 64 | 65 | linky! 66 | 67 | 68 | 69 | 70 | 71 | \"Fork 72 |

linky

73 | 74 | %s 75 | " 76 | (->> details 77 | (--map (format 78 | "
  • %s %s
  • " 79 | (car it) 80 | (or (cadr it) (car it)) 81 | (if (car it) 82 | (format "[%s]" (car it)) 83 | ""))) 84 | (s-join "\n")) 85 | linky/form-html))) 86 | 87 | (defun linky/store-add (username address name) 88 | "Add a site record for the USERNAME to `linky/store'. 89 | 90 | ADDRESS is the address of the record. NAME is an optional name 91 | to describe it. If not given then we just use the ADDRESS." 92 | (let ((record (gethash username linky/store))) 93 | (puthash username 94 | (cons 95 | (cons address (list (or name address))) 96 | record) 97 | linky/store))) 98 | 99 | (defun linky/store-add-user (username password) 100 | (if (db-get username linky/auth-db) 101 | :error 102 | ;; Else... 103 | (elnode-auth-user-add username password 'linky/auth-db))) 104 | 105 | (defun linky/register (httpcon) 106 | (let ((username (elnode-http-param httpcon "username")) 107 | (password (elnode-http-param httpcon "password"))) 108 | (if (not (and username password)) 109 | (elnode-send-redirect httpcon "/") 110 | ;; Else save the user 111 | (linky/store-add-user username password) 112 | (elnode-send-redirect httpcon "/")))) 113 | 114 | (defun linky (httpcon) 115 | (with-elnode-auth httpcon :linky-auth 116 | (let ((me (elnode-auth-username httpcon))) 117 | (elnode-method httpcon 118 | (GET (elnode-send-html httpcon (linky/store->html me))) 119 | (POST 120 | (let* ((address (elnode-http-param httpcon "a")) 121 | (name (or (elnode-http-param httpcon "n") address))) 122 | (linky/store-add me address name) 123 | (elnode-send-redirect httpcon "/"))))))) 124 | 125 | (defconst linky/docroot (expand-file-name "docroot") 126 | "Where we put static files.") 127 | 128 | (defun linky/ws (httpcon) 129 | "Webserver for linky." 130 | (elnode--webserver-handler-proc 131 | httpcon 132 | linky/docroot elnode-webserver-extra-mimetypes)) 133 | 134 | (defun linky-router (httpcon) 135 | "Simple router for `linky'." 136 | (elnode-dispatcher 137 | httpcon 138 | '(("^/-/\\(.*\\)" . linky/ws) 139 | ("^/register/" . linky/register) 140 | ("^/$" . linky)) 141 | :auth-scheme :linky-auth)) 142 | 143 | (elnode-start 144 | 'linky-router 145 | :port 8005 146 | :host "0.0.0.0") 147 | 148 | (provide 'linky) 149 | 150 | ;;; linky.el ends here 151 | -------------------------------------------------------------------------------- /linky-client.el: -------------------------------------------------------------------------------- 1 | ;;; linky-client.el --- a client for linky.elnode.org -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2014 Nic Ferrier 4 | 5 | ;; Author: Nic Ferrier 6 | ;; Keywords: hypermedia 7 | ;; Version: 0.0.2 8 | ;; Created: Fri Aug 29 23:07:28 BST 2014 9 | 10 | ;; This program is free software; you can redistribute it and/or modify 11 | ;; it under the terms of the GNU General Public License as published by 12 | ;; the Free Software Foundation, either version 3 of the License, or 13 | ;; (at your option) any later version. 14 | 15 | ;; This program is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with this program. If not, see . 22 | 23 | ;;; Commentary: 24 | 25 | ;; This is a curl based client for linky, a little HTTP-link tool 26 | ;; allowing you to post local links to an Internet site. This is 27 | ;; mostly useful if you're developing on multiple devices and you need 28 | ;; one to see the other without having to discover IPs (or indeed type 29 | ;; them in on fiddly mobile keyboards). 30 | 31 | ;; This client is based on sending a link to an elnode service being 32 | ;; hosted in your emacs, but may offer a generic interface in the 33 | ;; future, if I can be arsed. I have not packaged depended on elnode 34 | ;; because I may remove the dependency in the future. 35 | 36 | ;; The client sends requests to http://elnode.linky.org but you could 37 | ;; use any server, linky is available as a docker which you could just 38 | ;; run yourself. 39 | 40 | ;; See http://github.com/nicferrier/elnode-linky for more information 41 | ;; about linky, or 42 | ;; https://registry.hub.docker.com/u/nicferrier/elnode-linky for more 43 | ;; information about the docker. 44 | 45 | ;;; Code: 46 | 47 | ;;(require 'web) 48 | (require 'rx) 49 | 50 | (defun linky-web-client () 51 | ;; FIXME!!! this is not working because web can't remember cookies 52 | (web-http-post 53 | (lambda (con hdr data) 54 | (web-cookie-handler 55 | con hdr data 56 | (lambda (con hdr data) 57 | ))) 58 | :url "http://linky.elnode.org/login/" 59 | :data '(("username" . "nicferrier") 60 | ("password" . "cushions")))) 61 | 62 | 63 | (defvar linky-curl/username-history nil 64 | "History for usernames.") 65 | 66 | ;;;###autoload 67 | (defun linky-curl (port name &optional username password) 68 | "A simple linky client. 69 | 70 | Interactively it will complete a port and name from the 71 | `elnode-server-socket' and read USERNAME and PASSWORD from the 72 | minibuffer. 73 | 74 | Non-interactively you must pass USERNAME and PASSWORD for it to 75 | work." 76 | (interactive 77 | ;; Return the port of the server 78 | (let* ((completions 79 | (--keep 80 | (cons 81 | (format 82 | "%d %s" 83 | (car it) 84 | (let ((handler (elnode/con-lookup (cdr it) :elnode-http-handler))) 85 | (when (functionp handler) (documentation handler)))) 86 | (car it)) 87 | elnode-server-socket)) 88 | (chosen 89 | (kva 90 | (completing-read "Elnode server: " completions) 91 | completions))) 92 | (list chosen 93 | (format "%s" 94 | (let ((handler 95 | (elnode/con-lookup 96 | (kva chosen elnode-server-socket) 97 | :elnode-http-handler))) 98 | (when (functionp handler) (documentation handler))))))) 99 | (noflet ((curl (url cookie-flag data) ; a function to do curl 100 | (let* ((curl-cmd 101 | (format 102 | "curl -f -s -d %s -%s %s http://linky.elnode.org%s" 103 | data 104 | cookie-flag 105 | (expand-file-name "~/.linky") 106 | url)) 107 | (curl-args (split-string curl-cmd " "))) 108 | (with-temp-buffer 109 | (apply 'call-process "curl" nil t nil (cdr curl-args)) 110 | (s-trim (buffer-string)))))) 111 | (let* ((addr 112 | (format 113 | "http://%s:%d" 114 | (s-trim 115 | (shell-command-to-string 116 | "ip addr show wlan0 | sed -rne 's|.*inet (.*)/.*|\\1|p'")) 117 | port)) 118 | (post-data 119 | (web-to-query-string 120 | `(("a" . ,addr) 121 | ("n" . ,name))))) 122 | (unless (string-match-p 123 | (curl "/" "b" post-data) 124 | "

    redirecting you to /

    ") 125 | ;; Try and login to do it again... 126 | (let* ((data 127 | (apply 128 | 'format "username=%s&password=%s" 129 | (if (called-interactively-p 'interactive) 130 | (list 131 | (read-from-minibuffer 132 | "username: " nil nil nil 133 | 'linky-curl/username-history) 134 | (read-passwd "password: ")) 135 | (if (and username password) 136 | (list username password) 137 | (error "linky: you must supply a username and password")))))) 138 | (if (string-match-p 139 | (curl "/login/" "c" data) 140 | "

    redirecting you to /

    ") 141 | (curl "/" "b" post-data) 142 | ;; Else... 143 | (error "linky: login failed!"))))))) 144 | 145 | (provide 'linky-client) 146 | 147 | ;;; linky-client.el ends here 148 | --------------------------------------------------------------------------------