From 3ae7b05b9b2492e8df4c255e30e2786e813cd647 Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 10 Mar 2020 06:44:28 -0700 Subject: [PATCH] initial --- .dockerignore | 3 + .gitignore | 3 + Dockerfile | 75 +++++++++++++++++++++++++ Makefile | 15 +++++ Pipfile | 15 +++++ Pipfile.lock | 50 +++++++++++++++++ README.md | 18 ++++++ bin/tvidd | 9 +++ setup.py | 15 +++++ tvid/__init__.py | 2 + tvid/db.py | 16 ++++++ tvid/server.py | 130 ++++++++++++++++++++++++++++++++++++++++++++ views/displayid.tpl | 20 +++++++ views/nocookie.tpl | 10 ++++ views/style.tpl | 10 ++++ 15 files changed, 391 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 README.md create mode 100644 bin/tvidd create mode 100644 setup.py create mode 100644 tvid/__init__.py create mode 100644 tvid/db.py create mode 100644 tvid/server.py create mode 100644 views/displayid.tpl create mode 100644 views/nocookie.tpl create mode 100644 views/style.tpl diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..947d3f5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +dist/ +db.sqlite +build/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..947d3f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +dist/ +db.sqlite +build/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2cf867d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,75 @@ +# focal amd64 as of 2020-03-10 +FROM ubuntu@sha256:d050ed7278c16ff627e4a70d7d353f1a2ec74d8a0b66e5a865356d92f5f6d87b + +#######################################################################33 +## Mirror Setup +## - option to use local mirror to speed build +#######################################################################33 +ARG UBUNTU_MIRROR=http://archive.ubuntu.com/ubuntu +RUN echo "deb $UBUNTU_MIRROR focal main universe restricted multiverse" > /etc/apt/sources.list.new && \ +echo "deb $UBUNTU_MIRROR focal-updates main universe restricted multiverse" >> /etc/apt/sources.list.new && \ +echo "deb $UBUNTU_MIRROR focal-security main universe restricted multiverse" >> /etc/apt/sources.list.new && \ +echo "deb $UBUNTU_MIRROR focal-backports main universe restricted multiverse" >> /etc/apt/sources.list.new && \ +mv /etc/apt/sources.list.new /etc/apt/sources.list + +#######################################################################33 +## Versions +#######################################################################33 +# master as of 2020-03-10 +ARG PYENV_COMMIT=df9fa1dc30b6448ef8605e2c2d4dfc2a94d6a35d +ARG PYTHON_VERSION=3.8.1 + +#######################################################################33 +## Packages +#######################################################################33 +ARG DEBIAN_FRONTEND=noninteractive +RUN apt update && \ + apt upgrade -y && \ + apt install -y \ + build-essential \ + curl \ + git \ + libbz2-dev \ + libffi-dev \ + liblzma-dev \ + libncurses5-dev \ + libncursesw5-dev \ + libreadline-dev \ + libsqlite3-dev \ + libssl-dev \ + llvm \ + locales \ + make \ + python-openssl \ + tk-dev \ + wget \ + xz-utils \ + zlib1g-dev \ + && \ + mkdir -p /var/app && \ + git clone https://github.com/pyenv/pyenv.git /usr/local/pyenv && \ + cd /usr/local/pyenv && \ + git checkout $PYENV_COMMIT + +ENV PYENV_ROOT /usr/local/pyenv +ENV PATH $PYENV_ROOT/bin:$PATH + + +#######################################################################33 +## Python +#######################################################################33 +RUN pyenv install $PYTHON_VERSION && \ + pyenv global $PYTHON_VERISON && \ + pip3 install pipenv + +#######################################################################33 +## Install Deps +#######################################################################33 +ADD ./Pipfile ./Pipfile.lock /var/app/ +WORKDIR /var/app +RUN pipenv install --python $(which python3) + +#######################################################################33 +## Install App +#######################################################################33 +ADD ./* /var/app/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ece822b --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +export PYTHONPATH =: $(PWD) + +default: docker + +peinstall: + pipenv install --python $(shell which python3) + +serve: + pipenv run python ./bin/tvidd + +clean: + rm -rf build dist tvid.egg-info + +docker: + docker build --build-arg UBUNTU_MIRROR="http://ubuntu.datavi.be/ubuntu" . diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..e482e03 --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +bottle = "*" +pprint = "*" +sqlalchemy = "*" +bottle-sqlalchemy = "*" + +[requires] +python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..af6666b --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,50 @@ +{ + "_meta": { + "hash": { + "sha256": "7216cb0c490cfe6852be9bc21eeac5f3f08cf65b39f7316fae3580a387938016" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "bottle": { + "hashes": [ + "sha256:0819b74b145a7def225c0e83b16a4d5711fde751cd92bae467a69efce720f69e", + "sha256:43157254e88f32c6be16f8d9eb1f1d1472396a4e174ebd2bf62544854ecf37e7" + ], + "index": "pypi", + "version": "==0.12.18" + }, + "bottle-sqlalchemy": { + "hashes": [ + "sha256:ba6127f3aff2b78649781adbbee65518233dc481e9f9e32e3b050d1ad9551c17" + ], + "index": "pypi", + "version": "==0.4.3" + }, + "pprint": { + "hashes": [ + "sha256:c0fa22d1462351671ca098e9779bb26a23880011e93eea5f199a150ee7b92a16" + ], + "index": "pypi", + "version": "==0.1" + }, + "sqlalchemy": { + "hashes": [ + "sha256:b92d2de62e43499d85b1780274d1b562e5159c7996f6f04a9bb46cf681ced45f" + ], + "index": "pypi", + "version": "==1.3.14" + } + }, + "develop": {} +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..77fdd65 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# tvid + +This is an app that lets you set all the kiosk/display TVs in your +organization to the same URL. Each will be cookied with a unique ID that +will display on each display in big letters, with no preconfiguration +required. + +Log in to the admin panel and enter the URL for that TV ID, and within 60 +seconds, that display will bounce to that URL, or any other time it turns +on. + +# todo + +* make display id animate: https://codepen.io/stezu/pen/cmLrI + +# author + +sneak <[sneak@sneak.berlin](mailto:sneak@sneak.berlin)> diff --git a/bin/tvidd b/bin/tvidd new file mode 100644 index 0000000..7123ac3 --- /dev/null +++ b/bin/tvidd @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +from tvid import serve + +def main(): + serve() + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9f04bb2 --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_packages + +setup( + name='tvid', + version='0.0.1', + packages=find_packages(), + license='WTFPL', + long_description=open('README.md').read(), + entry_points = { + 'console_scripts': [ + 'tvidd = tvid.tvid:serve' + ], + }, +) + diff --git a/tvid/__init__.py b/tvid/__init__.py new file mode 100644 index 0000000..6640c20 --- /dev/null +++ b/tvid/__init__.py @@ -0,0 +1,2 @@ +from .server import serve + diff --git a/tvid/db.py b/tvid/db.py new file mode 100644 index 0000000..4b66f5d --- /dev/null +++ b/tvid/db.py @@ -0,0 +1,16 @@ +from sqlalchemy import Column, Sequence, Integer, String, DateTime +from sqlalchemy.ext.declarative import declarative_base + +from .server import SQLBASE + +Base = SQLBASE +# YAGNI just set admin pw in an env var for now and dont support changing it +# yet +#class Settings(Base): + +class TV(Base): + __tablename__ = 'tvs' + id = Column(Integer, Sequence('id_seq'), primary_key=True) + displayid = Column(String(20)) + lastSeen = Column(DateTime) + target = Column(String(255)) diff --git a/tvid/server.py b/tvid/server.py new file mode 100644 index 0000000..cad2385 --- /dev/null +++ b/tvid/server.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 + +# this is a quick hack, to be improved later. trying to do the simplest +# thing that will possibly work. + +## # TODO +## +## * put page content into templates/static files +## * clean up FIXME + +import bottle +from bottle import route, run, request, response, redirect +from bottle import HTTPError, template +from sqlalchemy.ext.declarative import declarative_base +from bottle.ext import sqlalchemy +from pprint import pprint +from sqlalchemy import create_engine +import os +import random +import string +from datetime import datetime + +VERSION = '0.0.1' +PORT = os.environ.get('PORT', 8080) +DEBUG = os.environ.get('DEBUG', False) +SQLITE_FILENAME = os.environ.get('SQLITE_FILENAME','/data/db.sqlite') +DATABASE_URL = os.environ.get('DATABASE_URL','sqlite:///' + SQLITE_FILENAME) +ADMINPSK = os.environ.get('ADMINPSK','hunter2') + +# sorry for global +SQLBASE = declarative_base() + +# FIXME make this skip letters not in base58 +def randomUpper(count): + return ''.join(random.choice(string.ascii_uppercase) for _ in range(count)) + +# FIXME maybe make the IDs longer +def genRandomTVID(): + tvid = str(randomUpper(3) + "-" + randomUpper(3)) + return tvid + +def serve(): + app = bottle.Bottle() + + # pull in models + from .db import TV + + engine = create_engine(DATABASE_URL, echo=True) + + plugin = sqlalchemy.Plugin( + engine, # SQLAlchemy engine created with create_engine function. + SQLBASE.metadata, # SQLAlchemy metadata, required only if create=True. + keyword='db', # Keyword used to inject session database in a route (default 'db'). + create=True, # If it is true, execute `metadata.create_all(engine)` when plugin is applied (default False). + commit=True, # If it is true, plugin commit changes after route is executed (default True). + use_kwargs=False + ) + + app.install(plugin) + + # here we cookie them if they have no cookie, then redirect them to the + # cookie'd value (whether preexisting or new). + @app.get('/') + def indexpage(): + c = request.get_cookie("tvid") + if c: + # redirect + redirect('/tv/' + c) + else: + newid = genRandomTVID() + response.set_cookie("tvid", newid) + redirect('/tv/' + newid) + + @app.get('/style.css') + def stylesheet(): + return template('style') + + # here we check to see if they have a redirect URL in the db. if they do + # we send them there. if they don't, we display their ID really big, + # reloading the page once per hour. + @app.get('/tv/') + def tvpage(db, displayid=None): + # FIXME regex check id to make sure displayid is right format, + # return error if not + + if id is None: + return template('nocookie') + + # check db for tv id + tv = db.query(TV).filter_by(displayid=displayid).first() + if tv: + tv.lastSeen = datetime.now() + db.add(tv) + if tv.target: + redirect(tv.target) + else: + return template('displayid', id=displayid, version=VERSION) + else: + # otherwise, just show their display ID bigly and keep them + # bouncing periodically until some admin configures them + # a target: + # update lastseen here: + newtv = TV(displayid=displayid,lastSeen=datetime.now()) + db.add(newtv) + return template('displayid', id=displayid, version=VERSION) + + # here we display the administration list of TVs if logged in + # if logged out then redirect to /login + # FIXME make this use sessions instead of just storing PSK in a cookie + # https://bottlepy.org/docs/dev/recipes.html + @app.get('/admin') + def adminpage(): + c = request.get_cookie("adminpw") + + # FIXME check their 'adminpw' cookie here, redirect to /loign + return "Hello World!" + + # here we ask for a password and cookie them and bounce them back to /admin + @app.get('/login') + def checklogin(): + raise NotImplementedError() + #response.set_cookie("adminpw", whatever) + redirect('/login') + + @app.get('/logut') + def logout(): + response.set_cookie("adminpw", "") + redirect('/login') + + app.run(host='0.0.0.0', port=PORT, debug=DEBUG) diff --git a/views/displayid.tpl b/views/displayid.tpl new file mode 100644 index 0000000..8d3f926 --- /dev/null +++ b/views/displayid.tpl @@ -0,0 +1,20 @@ + + + tv info page + + + + +
+

+ Display ID: +

+

{{id}}

+ + (They're only letters. I like India, O like + Oscar.) + +

Powered by tvid v{{version}}

+
+ + diff --git a/views/nocookie.tpl b/views/nocookie.tpl new file mode 100644 index 0000000..a6354c8 --- /dev/null +++ b/views/nocookie.tpl @@ -0,0 +1,10 @@ + + + tv info page + + + + +

Please enable cookies, they're required.

+ + diff --git a/views/style.tpl b/views/style.tpl new file mode 100644 index 0000000..ef0985b --- /dev/null +++ b/views/style.tpl @@ -0,0 +1,10 @@ +body { + font-family: sans-serif; + width: 100%; + text-align: center; + font-size: 20pt +} + +h1 { + font-size: 48pt; +}