Browse Source

initial

prod
Jeffrey Paul 6 months ago
commit
3ae7b05b9b
15 changed files with 391 additions and 0 deletions
  1. +3
    -0
      .dockerignore
  2. +3
    -0
      .gitignore
  3. +75
    -0
      Dockerfile
  4. +15
    -0
      Makefile
  5. +15
    -0
      Pipfile
  6. +50
    -0
      Pipfile.lock
  7. +18
    -0
      README.md
  8. +9
    -0
      bin/tvidd
  9. +15
    -0
      setup.py
  10. +2
    -0
      tvid/__init__.py
  11. +16
    -0
      tvid/db.py
  12. +130
    -0
      tvid/server.py
  13. +20
    -0
      views/displayid.tpl
  14. +10
    -0
      views/nocookie.tpl
  15. +10
    -0
      views/style.tpl

+ 3
- 0
.dockerignore View File

@@ -0,0 +1,3 @@
dist/
db.sqlite
build/

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
dist/
db.sqlite
build/

+ 75
- 0
Dockerfile View File

@@ -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/

+ 15
- 0
Makefile View File

@@ -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" .

+ 15
- 0
Pipfile View File

@@ -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"

+ 50
- 0
Pipfile.lock View File

@@ -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": {}
}

+ 18
- 0
README.md View File

@@ -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)>

+ 9
- 0
bin/tvidd View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python3

from tvid import serve

def main():
serve()

if __name__ == "__main__":
main()

+ 15
- 0
setup.py View File

@@ -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'
],
},
)


+ 2
- 0
tvid/__init__.py View File

@@ -0,0 +1,2 @@
from .server import serve


+ 16
- 0
tvid/db.py View File

@@ -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))

+ 130
- 0
tvid/server.py View File

@@ -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/<displayid>')
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)

+ 20
- 0
views/displayid.tpl View File

@@ -0,0 +1,20 @@
<html>
<head>
<title>tv info page</title>
<meta http-equiv="refresh" content="60">
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<div id="main">
<p>
<i>Display ID:</i>
</p>
<h1>{{id}}</h1>

<small>(They're only letters. I like India, O like
Oscar.)</small>

<p><small>Powered by tvid v{{version}}</small></p>
</div>
</body>
</html>

+ 10
- 0
views/nocookie.tpl View File

@@ -0,0 +1,10 @@
<html>
<head>
<title>tv info page</title>
<meta http-equiv="refresh" content="60; url=/">
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<h1>Please enable cookies, they're required.</h1>
</body>
</html>

+ 10
- 0
views/style.tpl View File

@@ -0,0 +1,10 @@
body {
font-family: sans-serif;
width: 100%;
text-align: center;
font-size: 20pt
}

h1 {
font-size: 48pt;
}

Loading…
Cancel
Save