diff --git a/.gitignore b/.gitignore index 7e769ef..b3b4ba6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ __pycache__ *tar.gz *sql flask_session -config.py +.env diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..77dea88 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,15 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + + diff --git a/Makefile b/Makefile deleted file mode 100644 index 7fbe5b1..0000000 --- a/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -setup: - python3 -m venv .venv - .venv/bin/pip install -r requirements.txt - -shell: - ./bin/cmd shell - -dev: - ./bin/dev - -prod: - ./bin/prod diff --git a/README.md b/README.md index d63e74d..c5eae00 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,99 @@ # SuchWow! -TBD +Yo. This is a goofy little CRUD app that interacts with wownero-wallet-rpc to allow people to post memes and get tipped in WOW. It uses [Wownero's SSO & Identity service](https://login.wownero.com/developer/docs/backend) as an authentication backend. + +It's lacking a lot of advanced functionality and features....but is pretty rock solid in operation. I rarely have to interact w/ my VPS, it just goes... + +It was created haphazardly and drunkenly (at varying times). It (hopefully) stays true to the core ethos of Wownero (I feel) which is, "fuck it, lgtm, ship it". Rough around the edges but overall works great. Was fun to whip together and a blast to see it grow with a community behind it! Thanks all! + +## Design + +Using the "account" mechanism in the wallets (not subaddresses). Every post gets it's own Wownero account within that running wallet RPC process and is mapped to it. Funds go into the account when tipped. The payout script just checks the balances of each account on an interval and sends funds to the user associated with the artwork associated with said account. ## Setup +There are quite a few prerequisites to run the web service: +* [register](https://login.wownero.com/developer/register) your app on [Wownero SSO](https://login.wownero.com/developer/docs/backend) +* install Wownero binaries/software +* setup secrets and config +* initialize new Wownero wallet and retain seed +* run the wownero-wallet-rpc process +* install local system Python dependencies +* initialize new sqlite db +* setup scheduled tasks to run payouts + +I like to ship with Docker, adjust how you'd prefer. + ``` -# initialize new wallet and retain seed -docker run --rm -it --name suchwow-wallet-init \ - -v $(pwd)/data:/root \ - lalanza808/wownero \ - wownero-wallet-cli \ - --daemon-address https://node.suchwow.xyz:443 \ - --generate-new-wallet /root/wow \ - --password zzzzzz \ - -# setup rpc process -docker run --rm -d --name suchwow-wallet \ - -v $(pwd)/data:/root \ - -p 8888:8888 \ - lalanza808/wownero \ - wownero-wallet-rpc \ - --daemon-address https://node.suchwow.xyz:443 \ - --wallet-file /root/wow \ - --password zzzzzz \ - --rpc-bind-port 8888 \ - --rpc-bind-ip 0.0.0.0 \ - --confirm-external-bind \ - --rpc-login xxxx:yyyy \ - --log-file /root/rpc.log - -# install python dependencies +# setup secrets in env file outside of git +cp env-example .env + +# register on wownero sso +# https://login.wownero.com/developer/register + +# inject generated keys into suchwow config +# create new secrets for new wallet, flask server, data, etc +vim .env + +# install docker +sudo apt-get install docker.io docker-compose -y +usermod -aG docker ubuntu +sudo -u ubuntu bash # login w/ new group perms + +# run wownero wallets via docker - store seed, ensure rpc running `docker ps` +./run_wallets.sh + +# install python dependencies locally +sudo apt-get install python3-venv -y python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt -# setup secrets in config file outside of git -cp suchwow/config.example.py suchwow/config.py -vim !$ +# initialize a new sqlite db +./manage.sh init + +# setup recurring payouts +# see crontab.txt for cron syntax example +crontab -e + +# run a dev server +./manage.sh run # access on localhost:5000 - flask local dev, not for prod + +# run a prod server +./manage.sh prod # run gunicorn on loopback - pass to nginx reverse proxy + +# kill prod server +pkill -e -f gunicorn ``` + +# Operational Tasks + +You'll want to promote a user as a moderator to review posts: + +``` +./manage.sh add_admin lza_menace +``` + +or remove them: + +``` +./manage.sh remove_admin lza_menace +``` + +Wallets bug out fairly often. Sometimes you might need to punch in a new daemon or restart a wallet: + +``` +docker stop suchwow-wallet-rpc +docker rm suchwow-wallet-rpc +vim .env +# modify DAEMON_URI +./run_wallets.sh +``` + +Manually run a payout: + +``` +./manage.py payout_users +``` + +There's other shit, but this is the most common. You'll get the hang of it. diff --git a/bin/cmd b/bin/cmd deleted file mode 100755 index fcd4f8b..0000000 --- a/bin/cmd +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -source .venv/bin/activate -export FLASK_APP=suchwow/app.py -export FLASK_SECRETS=config.py -export FLASK_DEBUG=1 -flask $@ diff --git a/bin/dev b/bin/dev deleted file mode 100755 index 45dbc6e..0000000 --- a/bin/dev +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -source .venv/bin/activate -export FLASK_APP=suchwow/app.py -export FLASK_SECRETS=config.py -export FLASK_DEBUG=1 -flask run diff --git a/bin/prod b/bin/prod deleted file mode 100755 index 5bc2663..0000000 --- a/bin/prod +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -BASE=data/gunicorn - -source .venv/bin/activate -export FLASK_APP=suchwow/app.py -export FLASK_SECRETS=config.py -export FLASK_DEBUG=0 -export FLASK_ENV=production - -mkdir -p $BASE - -pgrep -F $BASE/gunicorn.pid - -if [[ $? != 0 ]]; then - gunicorn \ - --bind 127.0.0.1:4000 "suchwow.app:app" \ - --daemon \ - --log-file $BASE/gunicorn.log \ - --pid $BASE/gunicorn.pid \ - --reload - sleep 1 - echo "Starting gunicorn with pid $(cat $BASE/gunicorn.pid)" -fi diff --git a/env-example b/env-example new file mode 100644 index 0000000..9cceb4a --- /dev/null +++ b/env-example @@ -0,0 +1,25 @@ +OIDC_URL=https://login.wownero.com/auth/realms/master/protocol/openid-connect +OIDC_CLIENT_ID=suchwow-dev +OIDC_CLIENT_SECRET=yyy-yyyyy-yyyyy-yy +OIDC_REDIRECT_URL=http://localhost:5000/auth +SECRET_KEY=ssssssssssss +DATA_FOLDER=/absolute/path/to/the/place/you/store/images +SERVER_NAME=localhost:5000 +WALLET_PATH=/absolute/path/to/the/place/you/store/wallet +WALLET_PASS=mytopsecretpass +WALLET_HOST=localhost +WALLET_PORT=8888 +WALLET_PROTO=http +WALLET_RPC_USER=suchwow +WALLET_RPC_PASS=again +DAEMON_URI=http://node.suchwow.xyz:34568 +PRAW_CLIENT_SECRET=xxxx +PRAW_CLIENT_ID=xxxxx +PRAW_USER_AGENT=xxxxx +PRAW_USERNAME=xxxxx +PRAW_PASSWORD=xxxx +DISCORD_URL=https://xxxx +MM_ICON=https://xxxx +MM_CHANNEL=xxxx +MM_USERNAME=xxxx +MM_ENDPOINT=https://xxxx diff --git a/manage.sh b/manage.sh new file mode 100755 index 0000000..3450c8c --- /dev/null +++ b/manage.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +source .venv/bin/activate +export FLASK_APP=suchwow/app.py +export FLASK_SECRETS=config.py +export FLASK_DEBUG=0 +export FLASK_ENV=production + +# override +export $(cat .env) + +if [[ ${1} == "prod" ]]; +then + export BASE=./data/gunicorn + mkdir -p $BASE + pgrep -F $BASE/gunicorn.pid + if [[ $? != 0 ]]; then + gunicorn \ + --bind 127.0.0.1:4000 "suchwow.app:app" \ + --daemon \ + --log-file $BASE/gunicorn.log \ + --pid $BASE/gunicorn.pid \ + --reload + sleep 2 + echo "Started gunicorn on 127.0.0.1:4000 with pid $(cat $BASE/gunicorn.pid)" + fi +else + flask $@ +fi diff --git a/requirements.txt b/requirements.txt index 99258ce..93e02c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ praw qrcode Pillow arrow +python-dotenv diff --git a/bin/setup b/run_wallets.sh similarity index 64% rename from bin/setup rename to run_wallets.sh index 557d49b..6249126 100755 --- a/bin/setup +++ b/run_wallets.sh @@ -3,29 +3,26 @@ set -e set +x -# these are only used for local development -WALLET_PATH="$(pwd)/data/suchwow-wallet" -WALLET_PASS="sdfj209rFLJDF29ruafj2)__!a@" -WALLET_RPC_USER="suchwow" -WALLET_RPC_PASS="y8YzL3cIW6Yeifa23s7Yng==" -DAEMON_URI="http://node.suchwow.xyz:34568" +# mainnet wownero + +export $(cat .env) if [ ! -d "$WALLET_PATH" ]; then # initialize new wallet and retain seed docker run --rm -it --name suchwow-wallet-init \ -v $WALLET_PATH:/root \ - lalanza808/wownero \ + lalanza808/wownero:latest \ wownero-wallet-cli \ --daemon-address $DAEMON_URI \ --generate-new-wallet /root/wow \ --password $WALLET_PASS fi -# setup rpc process -docker run --rm -d --name suchwow-wallet-rpc \ +# run rpc process +docker run --restart=always -d --name suchwow-wallet-rpc \ -v $WALLET_PATH:/root \ -p 8888:8888 \ - lalanza808/wownero \ + lalanza808/wownero:latest \ wownero-wallet-rpc \ --daemon-address $DAEMON_URI \ --wallet-file /root/wow \ diff --git a/suchwow/app.py b/suchwow/app.py index e068265..0b20273 100644 --- a/suchwow/app.py +++ b/suchwow/app.py @@ -78,11 +78,6 @@ def mod_queue(): def about(): return render_template("about.html") -@app.errorhandler(404) -def not_found(error): - flash("nothing there, brah") - return redirect(url_for("index")) - @app.cli.command("init") def init(): # create subdirs diff --git a/suchwow/config.example.py b/suchwow/config.example.py deleted file mode 100644 index 426f6d1..0000000 --- a/suchwow/config.example.py +++ /dev/null @@ -1,30 +0,0 @@ -from os import getenv - - -OIDC_URL = 'https://login.wownero.com/auth/realms/master/protocol/openid-connect', -OIDC_CLIENT_ID = 'suchwowxxx', -OIDC_CLIENT_SECRET = 'xxxxxxxxxx', -OIDC_REDIRECT_URL = 'http://localhost:5000/auth' -SECRET_KEY = 'yyyyyyyyyyyyy', -SESSION_TYPE = 'filesystem' -DATA_FOLDER = '/path/to/the/uploads' -ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} -MAX_CONTENT_LENGTH = 16 * 1024 * 1024 -WALLET_HOST = 'localhost' -WALLET_PORT = 8888 -WALLET_PROTO = 'http' -WALLET_USER = 'suchwow' -WALLET_PASS = 'zzzzzzzzzzzzzzz' -PRAW_CLIENT_SECRET = 'xxxxxxxx' -PRAW_CLIENT_ID = 'xxxxxxxx' -PRAW_USER_AGENT = 'suchwow-yyyy-python' -PRAW_USERNAME = 'xxxxxxxx' -PRAW_PASSWORD = 'xxxxxxxx' -SERVER_NAME = 'localhost' -DISCORD_URL = 'xxxxxxx' -BANNED_USERS = {'username': 'reason for the ban'} - -MM_ICON = getenv('MM_ICON', 'https://funding.wownero.com/static/wowdoge-a.jpg') -MM_CHANNEL = getenv('MM_CHANNEL', 'suchwow') -MM_USERNAME = getenv('MM_USERNAME', 'SuchWow!') -MM_ENDPOINT = getenv('MM_ENDPOINT', 'ppppppppppppppppppppppppp') diff --git a/suchwow/config.py b/suchwow/config.py new file mode 100644 index 0000000..2c318a1 --- /dev/null +++ b/suchwow/config.py @@ -0,0 +1,46 @@ +from os import getenv +from dotenv import load_dotenv + + +load_dotenv() + +# generated from https://login.wownero.com/developer/register +OIDC_URL = getenv('OIDC_URL', 'https://login.wownero.com/auth/realms/master/protocol/openid-connect') +OIDC_CLIENT_ID = getenv('OIDC_CLIENT_ID', 'suchwow-dev') +OIDC_CLIENT_SECRET = getenv('OIDC_CLIENT_SECRET', '') +OIDC_REDIRECT_URL = getenv('OIDC_REDIRECT_URL', 'http://localhost:5000/auth') + +# you specify something +SECRET_KEY = getenv('SECRET_KEY', 'yyyyyyyyyyyyy') # whatever you want it to be +DATA_FOLDER = getenv('DATA_FOLDER', '/path/to/uploads') # some stable storage path +SERVER_NAME = getenv('SERVER_NAME', 'localhost') # name of your DNS resolvable site (.com) +WALLET_HOST = getenv('WALLET_HOST', 'localhost') # +WALLET_PORT = int(getenv('WALLET_PORT', 8888)) # +WALLET_PROTO = getenv('WALLET_PROTO', 'http') # +WALLET_RPC_USER = getenv('WALLET_RPC_USER', 'suchwow') # +WALLET_RPC_PASS = getenv('WALLET_RPC_PASS', 'suchwow') # +WALLET_PASS = getenv('WALLET_PASS', 'zzzzzzz') # You specify all these wallet details in .env + +# Optional for banning users who post crazy shit (they do) +BANNED_USERS = {'username': 'reason for the ban'} + +# Optional for posting to Reddit +PRAW_CLIENT_SECRET = getenv('PRAW_CLIENT_SECRET', None) +PRAW_CLIENT_ID = getenv('PRAW_CLIENT_ID', None) +PRAW_USER_AGENT = getenv('PRAW_USER_AGENT', None) +PRAW_USERNAME = getenv('PRAW_USERNAME', None) +PRAW_PASSWORD = getenv('PRAW_PASSWORD', None) + +# Optional for posting to Discord +DISCORD_URL = getenv('DISCORD_URL', None) + +# Optional for posting to Mattermost +MM_ICON = getenv('MM_ICON', 'https://funding.wownero.com/static/wowdoge-a.jpg') +MM_CHANNEL = getenv('MM_CHANNEL', 'suchwow') +MM_USERNAME = getenv('MM_USERNAME', 'SuchWow!') +MM_ENDPOINT = getenv('MM_ENDPOINT', 'ppppppppppppppppppppppppp') + +# defaults +SESSION_TYPE = 'filesystem' +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} +MAX_CONTENT_LENGTH = 16 * 1024 * 1024 diff --git a/suchwow/models.py b/suchwow/models.py index e9843c5..823e3e8 100644 --- a/suchwow/models.py +++ b/suchwow/models.py @@ -6,7 +6,7 @@ from suchwow import wownero from suchwow import config -db = SqliteDatabase(f"{config.DATA_FOLDER}/db/sqlite.db") +db = SqliteDatabase(f"{config.DATA_FOLDER}/sqlite.db") class Post(Model): id = AutoField() diff --git a/suchwow/wownero.py b/suchwow/wownero.py index 9c7cfd5..73e65ff 100644 --- a/suchwow/wownero.py +++ b/suchwow/wownero.py @@ -13,8 +13,8 @@ class Wallet(object): self.host = config.WALLET_HOST self.port = config.WALLET_PORT self.proto = config.WALLET_PROTO - self.username = config.WALLET_USER - self.password = config.WALLET_PASS + self.username = config.WALLET_RPC_USER + self.password = config.WALLET_RPC_PASS self.endpoint = '{}://{}:{}/json_rpc'.format( self.proto, self.host, self.port )