Compare commits
	
		
			2 Commits
		
	
	
		
			d7a0167cd6
			...
			469785ee33
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 469785ee33 | |||
| eb0f19b109 | 
							
								
								
									
										29
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								README.md
									
									
									
									
									
								
							@@ -2,12 +2,10 @@
 | 
			
		||||
 | 
			
		||||
### Sec:
 | 
			
		||||
 | 
			
		||||
* This repo is public. Mind cred slip-ups.
 | 
			
		||||
* Please note changes to /etc/sshd/sshd_conf made by lll script. If different method is used, audit manually.
 | 
			
		||||
* Note app Dockerfile debug console, found at /console. Werkzeug/flask is WILDLY insecure if left in dev/dbg.
 | 
			
		||||
* Avoid docker socks stuff.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
- This repo is public. Mind cred slip-ups.
 | 
			
		||||
- Please note changes to /etc/sshd/sshd_conf made by lll script. If different method is used, audit manually.
 | 
			
		||||
- Note app Dockerfile debug console, found at /console. Werkzeug/flask is WILDLY insecure if left in dev/dbg.
 | 
			
		||||
- Avoid docker socks stuff.
 | 
			
		||||
 | 
			
		||||
### Install:
 | 
			
		||||
 | 
			
		||||
@@ -49,19 +47,28 @@ set up cron job for script
 | 
			
		||||
        pmb-pf - git clone of my mail thing
 | 
			
		||||
        other - ref and non-sensitive files for dns
 | 
			
		||||
 | 
			
		||||
### Timeline:
 | 
			
		||||
### Setup cheat:
 | 
			
		||||
 | 
			
		||||
set up certbot dns\
 | 
			
		||||
see tar of cert dir with script
 | 
			
		||||
- set up certbot dns (prod)
 | 
			
		||||
- see tar of cert dir with script (prod)
 | 
			
		||||
- flask vs uwsgi in backend compose section (prod)
 | 
			
		||||
- build vs local image in pmb-pf compose section
 | 
			
		||||
- git clone pmb-pf
 | 
			
		||||
- copy example .env in root dir
 | 
			
		||||
- copy example .env in pmb-pf
 | 
			
		||||
- copy example conf in proxy
 | 
			
		||||
- do pmb-pf setup, and adjust root .env
 | 
			
		||||
- mind backend config db settings
 | 
			
		||||
 | 
			
		||||
### Notes:
 | 
			
		||||
This repo is minimally-sensitive. Falling outside the repo dir structure are reference awesome-compose files used as baseline -- nginx-flask-mysql -- and certs, containing letsencrypt script. Script may be backed up into repo carefully, sanitizing any tkens.
 | 
			
		||||
 | 
			
		||||
TODO: gitea subdomain will require wildcard cert -- therefore "*.oily.dad" AND "oily.dad" DONE
 | 
			
		||||
 | 
			
		||||
### Changing gitea subdomain:
 | 
			
		||||
 | 
			
		||||
Find in proxy/conf.\
 | 
			
		||||
Find in gitea conf.\
 | 
			
		||||
Rebuild images.
 | 
			
		||||
 | 
			
		||||
### Todo:
 | 
			
		||||
- gitea subdomain will require wildcard cert -- therefore "*.oily.dad" AND "oily.dad" DONE
 | 
			
		||||
- move more stuff from backend config into root .env
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
venv
 | 
			
		||||
migrations
 | 
			
		||||
zapp.db
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,11 @@
 | 
			
		||||
# syntax=docker/dockerfile:1.4
 | 
			
		||||
FROM python:3-slim-bookworm AS builder
 | 
			
		||||
 | 
			
		||||
RUN apt update && apt install -y libmariadb-dev gcc
 | 
			
		||||
# Second line optional/debug/qol
 | 
			
		||||
RUN apt update && apt install -y \
 | 
			
		||||
    libmariadb-dev gcc \
 | 
			
		||||
    mariadb-client
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
WORKDIR /code
 | 
			
		||||
COPY requirements.txt /code
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,14 @@ pip install flask-migrate
 | 
			
		||||
pip install flask-login
 | 
			
		||||
pip install email-validator
 | 
			
		||||
pip install pydenticon
 | 
			
		||||
Prod only, require sys packages:
 | 
			
		||||
pip install mariadb
 | 
			
		||||
pip install uwsgi
 | 
			
		||||
...
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Freeze/requirements.txt. Better to audit this inside python:3-bookworm-slim container.
 | 
			
		||||
```
 | 
			
		||||
pip freeze > requirements.txt
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -36,8 +42,11 @@ flask db downgrade base
 | 
			
		||||
flask db upgrade
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Full reset:
 | 
			
		||||
Full reset or maria init:
 | 
			
		||||
```
 | 
			
		||||
sql:
 | 
			
		||||
drop table users;
 | 
			
		||||
drop table posts;
 | 
			
		||||
rm app.db
 | 
			
		||||
rm -r migrations
 | 
			
		||||
flask db init
 | 
			
		||||
 
 | 
			
		||||
@@ -26,11 +26,12 @@ if not app.debug:
 | 
			
		||||
        app.logger.addHandler(mail_handler)
 | 
			
		||||
 | 
			
		||||
    if app.config['DC_LOGGING']:
 | 
			
		||||
        print('#################### DEBUGHERE', file=sys.stderr)
 | 
			
		||||
        print('#################### TEST PRINT STDERR DEBUG', file=sys.stderr)
 | 
			
		||||
        dclog = logging.StreamHandler(stream=sys.stderr)
 | 
			
		||||
        dclog.setLevel(logging.INFO)
 | 
			
		||||
        dclog.propagate = False
 | 
			
		||||
        app.logger.addHandler(dclog)
 | 
			
		||||
        app.logger.info('@@@@@@@@@@@@@@@@@@@@@ TEST LOGGER INFO MESSAGE')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from app import routes, models, errors
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,13 @@ import pydenticon, hashlib, base64
 | 
			
		||||
from app import db, login
 | 
			
		||||
from flask_login import UserMixin
 | 
			
		||||
 | 
			
		||||
followers = sa.Table(
 | 
			
		||||
        'followers',
 | 
			
		||||
        db.metadata,
 | 
			
		||||
        sa.Column('follower_id', sa.Integer, sa.ForeignKey('user.id'), primary_key=True),
 | 
			
		||||
        sa.Column('followed_id', sa.Integer, sa.ForeignKey('user.id'), primary_key=True)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class User(UserMixin, db.Model):
 | 
			
		||||
    id: so.Mapped[int] = so.mapped_column(primary_key=True)
 | 
			
		||||
    username: so.Mapped[str] = so.mapped_column(sa.String(64), index=True, unique=True)
 | 
			
		||||
@@ -17,6 +24,16 @@ class User(UserMixin, db.Model):
 | 
			
		||||
    posts: so.WriteOnlyMapped['Post'] = so.relationship(back_populates='author')
 | 
			
		||||
    about_me: so.Mapped[Optional[str]] = so.mapped_column(sa.String(140))
 | 
			
		||||
    last_seen: so.Mapped[Optional[datetime]] = so.mapped_column(default=lambda: datetime.now(timezone.utc))
 | 
			
		||||
    following: so.WriteOnlyMapped['User'] = so.relationship(
 | 
			
		||||
            secondary=followers,
 | 
			
		||||
            primaryjoin=(followers.c.follower_id == id),
 | 
			
		||||
            secondaryjoin=(followers.c.followed_id == id),
 | 
			
		||||
            back_populates='followers')
 | 
			
		||||
    followers: so.WriteOnlyMapped['User'] = so.relationship(
 | 
			
		||||
            secondary=followers,
 | 
			
		||||
            primaryjoin=(followers.c.followed_id == id),
 | 
			
		||||
            secondaryjoin=(followers.c.follower_id == id),
 | 
			
		||||
            back_populates='following')
 | 
			
		||||
 | 
			
		||||
    def set_password(self, password):
 | 
			
		||||
        self.password_hash = generate_password_hash(password)
 | 
			
		||||
@@ -49,6 +66,36 @@ class User(UserMixin, db.Model):
 | 
			
		||||
        pngloc = os.path.join(basedir, 'usercontent', 'identicon', digest + '.png')
 | 
			
		||||
        return pngloc
 | 
			
		||||
 | 
			
		||||
    def follow(self, user):
 | 
			
		||||
        if not self.is_following(user):
 | 
			
		||||
            self.following.add(user)
 | 
			
		||||
    def unfollow(self, user):
 | 
			
		||||
        if self.is_following(user):
 | 
			
		||||
            self.following.remove(user)
 | 
			
		||||
    def is_following(self, user):
 | 
			
		||||
        query = self.following.select().where(User.id == user.id)
 | 
			
		||||
        return db.session.scalar(query) is not None
 | 
			
		||||
    def followers_count(self):
 | 
			
		||||
        query = sa.select(sa.func.count()).select_from(self.followers.select().subquery())
 | 
			
		||||
        return db.session.scalar(query)
 | 
			
		||||
    def following_count(self):
 | 
			
		||||
        query = sa.select(sa.func.count()).select_from(self.following.select().subquery())
 | 
			
		||||
        return db.session.scalar(query)
 | 
			
		||||
    def following_posts(self):
 | 
			
		||||
        Author = so.aliased(User)
 | 
			
		||||
        Follower = so.aliased(User)
 | 
			
		||||
        return (
 | 
			
		||||
                sa.select(Post)
 | 
			
		||||
                .join(Post.author.of_type(Author))
 | 
			
		||||
                .join(Author.followers.of_type(Follower), isouter=True)
 | 
			
		||||
                .where(sa.or_(
 | 
			
		||||
                    Follower.id == self.id
 | 
			
		||||
                    Author.id == self.id
 | 
			
		||||
                ))
 | 
			
		||||
                .group_by(Post)
 | 
			
		||||
                .order_by(Post.timestamp.desc())
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return '<User {}>'.format(self.username)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@ basedir = os.path.abspath(os.path.dirname(__file__))
 | 
			
		||||
 | 
			
		||||
class Config:
 | 
			
		||||
    SECRET_KEY = os.environ.get('FLASK_SECRET_KEY') or 'flasksk'
 | 
			
		||||
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'zapp.db')
 | 
			
		||||
    #SQLALCHEMY_DATABASE_URI = 'mariadb+mariadbconnector://flasku:' + os.environ.get('MYSQL_PASSWORD') + '@db:3306/flask'
 | 
			
		||||
    #SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'zapp.db')
 | 
			
		||||
    SQLALCHEMY_DATABASE_URI = 'mariadb+mariadbconnector://flasku:' + os.environ.get('MYSQL_PASSWORD') + '@db:3306/flask'
 | 
			
		||||
 | 
			
		||||
    #MAIL_SERVER = 'pmb'
 | 
			
		||||
    MAIL_SERVER = ''
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								backend/dbdb.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								backend/dbdb.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
# Okay to publish -- creds are local dev only
 | 
			
		||||
 | 
			
		||||
mariadb -hdb -uflasku -pflaskp flask
 | 
			
		||||
@@ -21,5 +21,9 @@ SQLAlchemy==2.0.31
 | 
			
		||||
typing_extensions==4.12.2
 | 
			
		||||
Werkzeug==3.0.3
 | 
			
		||||
WTForms==3.1.2
 | 
			
		||||
uwsgi
 | 
			
		||||
mariadb
 | 
			
		||||
mariadb==1.1.10
 | 
			
		||||
packaging==24.1
 | 
			
		||||
setuptools==72.1.0
 | 
			
		||||
uWSGI==2.0.26
 | 
			
		||||
wheel==0.43.0
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								backend/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								backend/tests.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
import os
 | 
			
		||||
os.environ['DATABASE_URL'] = 'sqlite://'
 | 
			
		||||
 | 
			
		||||
from datetime import datetime, timezone, timedelta
 | 
			
		||||
 | 
			
		||||
@@ -28,9 +28,12 @@ services:
 | 
			
		||||
    build:
 | 
			
		||||
      context: backend
 | 
			
		||||
      target: builder
 | 
			
		||||
    restart: always
 | 
			
		||||
    # Next two are only debug, used without restart
 | 
			
		||||
    stdin_open: true
 | 
			
		||||
    tty: true
 | 
			
		||||
    #restart: always
 | 
			
		||||
    # Comment following line to use flask (1worker, dev), uncomment to use uwsgi (wsgi)
 | 
			
		||||
    #command: ["uwsgi", "--http", "0.0.0.0:8000", "--master", "-p", "4", "-w", "app:server"]
 | 
			
		||||
    #command: ["uwsgi", "--http", "0.0.0.0:8000", "--master", "-p", "4", "-w", "microblog:app"]
 | 
			
		||||
    environment:
 | 
			
		||||
      - MYSQL_USER=flasku
 | 
			
		||||
      #- MYSQL_PASSWORD=flaskp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								dotenv
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								dotenv
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ DOTENV_MYSQL_ROOT_PASSWORD=rootp
 | 
			
		||||
DOTENV_MYSQL_GITEA_PASSWORD=giteap
 | 
			
		||||
DOTENV_MYSQL_FLASK_PASSWORD=flaskp
 | 
			
		||||
 | 
			
		||||
GITEA_MAIL_FROM=
 | 
			
		||||
GITEA_MAIL_FROM=gitea@gitea.changeme
 | 
			
		||||
 | 
			
		||||
# Build ARG GPG_PP. May still need to be empty to avoid breakage.
 | 
			
		||||
BUILD_GPG_PP=
 | 
			
		||||
@@ -18,3 +18,7 @@ DOTENV_TOKEN_I=dti
 | 
			
		||||
 | 
			
		||||
# Consequential token: protect
 | 
			
		||||
DOTENV_TOKEN_C=dtc
 | 
			
		||||
 | 
			
		||||
# Destination address for handler mailer
 | 
			
		||||
ADMIN_EMAIL="email@email.changeme"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user