Skip to content

Commit

Permalink
templating, docker, actions, ...
Browse files Browse the repository at this point in the history
  • Loading branch information
m0wer committed Apr 6, 2024
1 parent 1a7d122 commit f91c07e
Show file tree
Hide file tree
Showing 18 changed files with 278 additions and 36 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
MAX_WORKERS=1
LOGURU_LEVEL=info
DATA_PATH=./data/
CONF_PATH=./conf/
WEB_PORT=8000
ADMIN_SECRET=changeme
50 changes: 50 additions & 0 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: images build

on:
push:
branches:
- master
paths:
- 'app/**'

env:
REGISTRY: ghcr.io

jobs:
build-and-push-images:
runs-on: ubuntu-latest
strategy:
fail-fast: false
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
12 changes: 12 additions & 0 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: pre-commit

on:
push:

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- uses: pre-commit/action@v3.0.1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ __pycache__

static_pages/
data/
conf/
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:

- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.0
hooks:
# Run the linter.
- id: ruff
args: [ --fix ]
# Run the formatter.
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.9.0'
hooks:
- id: mypy
# add types-requests
additional_dependencies: [ 'types-PyYAML' ]
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11

COPY ./requirements.txt /app/
RUN pip install --no-cache-dir -r /app/requirements.txt

COPY ./app/ /app/
COPY ./template/ /app/template/
WORKDIR /app/

VOLUME /app/data
VOLUME /app/conf
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Landing generator

Generate landing pages wih email collection in minutes.

## Run it

First, set up the configuration with:

```shell
mkdir -p ./conf
cp config.example.yaml ./conf/config.yaml
# edit the config file to add or change the default landing pages
```


### Docker

```shell
docker run -it --rm \
-p 8000:80 \
--name landing_generator \
-v ./conf/:/app/conf/:ro \
-v ./data/:/app/data/ \
-e ADMIN_SECRET=secret \
ghcr.io/m0wer/landing_generator:master
```

### Docker compose

```shell
cp .env.example .env
# edit the .env file
docker compose up -d --build
```
28 changes: 13 additions & 15 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


@asynccontextmanager
async def lifespan(app: FastAPI) -> None:
async def lifespan(app: FastAPI):
"""Function to run at startup and shutdown."""
# create data directory if it doesn't exist
DATA_PATH.mkdir(exist_ok=True)
Expand All @@ -32,18 +32,13 @@ async def lifespan(app: FastAPI) -> None:
)


app.mount("/", StaticFiles(directory=STATIC_PAGES_DIR, html=True), name="static")


class SiteEmail(BaseModel):
email: EmailStr = (Field(..., title="Email", description="Email address to save."),)
site: str = (
Field(
...,
title="Site",
description="Site name to save email for.",
pattern="^[a-zA-Z0-9_]+$",
),
email: EmailStr = Field(..., title="Email", description="Email address to save.")
site: str = Field(
...,
title="Site",
description="Site name to save email for.",
pattern="^[a-zA-Z0-9_]+$",
)


Expand All @@ -63,12 +58,12 @@ async def post_email(
f.write(CSV_HEADER)
# append email and timestamp to file
with open(SITE_DATA_PATH, "a") as f:
f.write(f"{email},{datetime.now().isoformat()}\n")
f.write(f"{email},{datetime.utcnow().isoformat()}\n")


def check_secret(
request: Request,
) -> bool:
) -> None:
"""Check if the authorization token is correct."""
if request.headers.get("Authorization") == ADMIN_SECRET:
return Response(status_code=401, content="Wrong secret.")
Expand Down Expand Up @@ -98,6 +93,9 @@ async def get_emails(
# read and return file contents
with open(SITE_DATA_PATH, "r") as f:
return [
{"email": line.split(",")[0], "timestamp": line.split(",")[1]}
{"email": line.split(",")[0], "timestamp": line.split(",")[1][:-1]}
for line in f.readlines()[1:]
]


app.mount("/", StaticFiles(directory=STATIC_PAGES_DIR, html=True), name="static")
4 changes: 4 additions & 0 deletions app/prestart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#! /usr/bin/env bash

cd /app
python scripts/render_sites.py
31 changes: 31 additions & 0 deletions app/scripts/render_sites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Module to render the template for the configured sites."""

import yaml
from pathlib import Path
import shutil
from jinja2 import Environment, FileSystemLoader
from loguru import logger

TEMPLATE_SRC: Path = Path("./template/src/")
CONF_PATH: Path = Path("./conf/config.yaml")
STATIC_PAGES_DIR: Path = Path("./static_pages")

if __name__ == "__main__":
with open(CONF_PATH, "r") as conf_file:
conf = yaml.safe_load(conf_file)

for site in conf["sites"]:
page_identifier = site["page_identifier"]
logger.info(f"Rendering site: {page_identifier}")
site_path: Path = STATIC_PAGES_DIR / page_identifier

# recursive copy of template files to site directory
shutil.copytree(TEMPLATE_SRC, site_path)

# replace jinja variables in index.html
env = Environment(loader=FileSystemLoader(site_path))
template = env.get_template("index.html")
rendered = template.render(**site)

with open(site_path / "index.html", "w") as f:
f.write(rendered)
16 changes: 16 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---

sites:
- page_identifier: landing_generator
title: Landing Generator
tagline: Generate landing pages wih email collection in minutes.
description: Generate landing pages wih email collection in minutes.
item_1_title: Validate your idea
item_1_description: Create a landing page to validate your idea.
item_2_title: Collect emails
item_2_description: You will have an audience ready for when you launch.
item_3_title: Self hosted
item_3_description: Host your landing page on your own server.
item_4_title: Secure
item_4_description: Your data is safe and secure and not shared with anyone.
button_text: Get more info
18 changes: 18 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
services:

backend:
image: ghcr.io/m0wer/landing_generator:master
container_name: landing-generator
build:
context: .
environment:
ROOT_PATH: "/api"
MAX_WORKERS: ${MAX_WORKERS:-1}
LOGURU_LEVEL: ${LOGURU_LEVEL:-INFO}
ADMIN_SECRET: ${ADMIN_SECRET:-admin}
volumes:
- ${DATA_PATH:-./data/}:/app/data/
- ${CONF_PATH:-./conf/}:/app/conf/
ports:
- ${WEB_PORT:-8000}:80
restart: unless-stopped
3 changes: 3 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
email-validator
fastapi
gunicorn
Jinja2
loguru
pydantic
pyyaml
uvicorn
16 changes: 10 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile
#
Expand All @@ -14,8 +14,6 @@ dnspython==2.6.1
# via email-validator
email-validator==2.1.1
# via -r requirements.in
exceptiongroup==1.2.0
# via anyio
fastapi==0.110.1
# via -r requirements.in
gunicorn==21.2.0
Expand All @@ -26,6 +24,12 @@ idna==3.6
# via
# anyio
# email-validator
jinja2==3.1.3
# via -r requirements.in
loguru==0.7.2
# via -r requirements.in
markupsafe==2.1.5
# via jinja2
packaging==24.0
# via gunicorn
pydantic==2.6.4
Expand All @@ -34,16 +38,16 @@ pydantic==2.6.4
# fastapi
pydantic-core==2.16.3
# via pydantic
pyyaml==6.0.1
# via -r requirements.in
sniffio==1.3.1
# via anyio
starlette==0.37.2
# via fastapi
typing-extensions==4.11.0
# via
# anyio
# fastapi
# pydantic
# pydantic-core
# uvicorn
uvicorn==0.29.0
# via -r requirements.in
13 changes: 2 additions & 11 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile requirements_dev.in
#
Expand All @@ -10,8 +10,6 @@ click==8.1.7
# via
# -c requirements.txt
# pip-tools
importlib-metadata==7.1.0
# via build
packaging==24.0
# via
# -c requirements.txt
Expand All @@ -22,15 +20,8 @@ pyproject-hooks==1.0.0
# via
# build
# pip-tools
tomli==2.0.1
# via
# build
# pip-tools
# pyproject-hooks
wheel==0.43.0
# via pip-tools
zipp==3.18.1
# via importlib-metadata

# The following packages are considered to be unsafe in a requirements file:
# pip
Expand Down
2 changes: 0 additions & 2 deletions template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,5 @@ The following variables are available for substitution:
- `{{ item_4_title }}` - Title for bottom right item in the gridbox
- `{{ item_4_description }}` - Description for bottom right item in the gridbox
- `{{ button_text }}` - Text for the button next to the email input field
- `{{ form_action_url }}` - URL where the form should post the `email` field

> Form method is always POST
Loading

0 comments on commit f91c07e

Please sign in to comment.