Skip to content

Commit 765e024

Browse files
committed
initial commit
0 parents  commit 765e024

9 files changed

Lines changed: 237 additions & 0 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches: [master]
6+
workflow_dispatch:
7+
8+
jobs:
9+
deploy:
10+
runs-on: ubuntu-24.04
11+
container:
12+
image: alpine/ansible:2.18.1
13+
options: --cap-add=NET_ADMIN --device=/dev/net/tun
14+
env:
15+
ANSIBLE_HOST_KEY_CHECKING: "False"
16+
ANSIBLE_PRIVATE_KEY_FILE: ~/.ssh/id_ed25519
17+
UV_LINK_MODE: copy
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- run: apk add make coreutils curl sudo tar
23+
24+
- uses: astral-sh/setup-uv@v5
25+
with:
26+
enable-cache: true
27+
28+
- uses: tailscale/github-action@v3
29+
with:
30+
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
31+
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
32+
tags: tag:ci
33+
34+
- name: Deploy
35+
uses: dawidd6/action-ansible-playbook@v3
36+
with:
37+
playbook: deploy.yml
38+
inventory: {{ cookiecutter.deploy_host }}
39+
key: ${{ secrets.DEPLOY_SSH_KEY }}

.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.aider*
2+
3+
# Byte-compiled / optimized / DLL files
4+
__pycache__/
5+
*.py[cod]
6+
*$py.class
7+
8+
# Distribution / packaging
9+
dist/
10+
build/
11+
*.egg-info/
12+
13+
# Virtual environments
14+
.venv/
15+
venv/
16+
env/
17+
ENV/
18+
19+
# Testing
20+
.coverage
21+
htmlcov/
22+
.pytest_cache/
23+
24+
# IDE specific files
25+
.idea/
26+
.vscode/
27+
*.swp
28+
*.swo
29+
30+
# OS specific files
31+
.DS_Store

.rsync-filter

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
- /.*
2+
- /*.yml
3+
- /*.sqlite*
4+
- __pycache__
5+
- /ansible*
6+
- /hosts
7+
- /etc
8+
- /tmp
9+
- /files
10+
- /images
11+
- *.sw*
12+
- *.git*
13+
- *.bak
14+
- *.sqlite*
15+
- *.pyc
16+
- *.test
17+
- .env*
18+
- *env-*
19+
- .DS_Store
20+
- /localpy
21+
- local_settings.py
22+
- Makefile

Makefile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
.DEFAULT_GOAL := deploy
2+
SHELL := /usr/bin/env -S bash -O globstar # makes work globs like **/*.py
3+
HOST ?= {{ cookiecutter.deploy_host }}
4+
PROJECT ?= {{ cookiecutter.project_name }}
5+
ENV ?= prod
6+
SLUG := $(PROJECT)-$(ENV)
7+
PREFIX := /var/www/$(SLUG)
8+
USER := {{ cookiecutter.deploy_user }}
9+
ANSIBLE := ansible-playbook deploy.yml -i $(HOST), -e project=$(PROJECT) -u $(USER)
10+
SSH := ssh -t $(USER)@$(HOST)
11+
12+
deploy: linter
13+
uv sync
14+
$(ANSIBLE) --skip-tags=full
15+
16+
init: clean githooks
17+
uv venv -q
18+
uv sync
19+
20+
clean:
21+
rm -rf .venv db.sqlite3 __pycache__
22+
23+
githooks:
24+
git config --local core.hooksPath .githooks
25+
26+
linter:
27+
python -m py_compile **/*.py
28+
uvx isort -q **/*.py
29+
uvx ruff format -q --line-length 140 **/*.py
30+
uvx ruff check -q --ignore F401 **/*.py
31+
32+
safety:
33+
uv tool run safety scan -o bare
34+
35+
full-deploy:
36+
uv sync
37+
$(ANSIBLE)
38+
39+
logs:
40+
ssh -t $(HOST) less +GF /var/log/supervisor/$(SLUG).log
41+
42+
migrate:
43+
./manage.py makemigrations db
44+
$(SSH) rm -rf $(PREFIX)/db/migrations
45+
make deploy
46+
$(SSH) uv run --directory $(PREFIX) manage.py migrate
47+
48+
restart:
49+
$(ANSIBLE) --tags restart

ansible.cfg

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[defaults]
2+
retry_files_enabled = False
3+
deprecation_warnings = False
4+
command_warnings = False
5+
remote_user = deploy
6+
interpreter_python = auto_legacy_silent
7+
8+
[ssh_connection]
9+
pipelining = True
10+
ssh_args = -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no

cookiecutter.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"project_name": "My Project",
3+
"project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}",
4+
"description": "A Python project created with cookiecutter",
5+
"author_name": "Your Name",
6+
"author_email": "your.email@example.com",
7+
"deploy_host": "my.host",
8+
"deploy_user": "deploy"
9+
}

deploy.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env ansible-playbook
2+
---
3+
- name: deploy bot
4+
hosts: all
5+
strategy: free
6+
gather_facts: false
7+
vars:
8+
env: prod
9+
slug: "{{ project }}-{{ env }}"
10+
prefix: /var/www/{{ slug }}
11+
user: deploy
12+
group: "{{ user }}"
13+
tasks:
14+
- name: remove existing virtualenv
15+
file: path="{{ prefix }}/.venv" state=absent
16+
tags: full
17+
18+
- name: upload directory {{ playbook_dir }} to {{ prefix }}
19+
synchronize: src="{{ playbook_dir }}/" dest={{ prefix }}
20+
21+
- name: cleanup UV cache
22+
command: uv cache clean
23+
args:
24+
chdir: "{{ prefix }}"
25+
tags: full
26+
27+
- name: create virtualenv
28+
shell: uv venv -q
29+
args:
30+
chdir: "{{ prefix }}"
31+
tags: full
32+
33+
- name: install dependencies
34+
shell: uv sync --frozen || uv sync --frozen --refresh
35+
args:
36+
chdir: "{{ prefix }}"
37+
38+
- name: check if env-{{ env }} file exists locally
39+
local_action: stat path=env-{{ env }}
40+
register: env_file
41+
42+
- name: copy secrets
43+
copy: src=env-{{ env }} dest={{ prefix }}/.env owner={{ user }} group={{ group }} mode=0400
44+
when: env_file.stat.exists
45+
ignore_errors: true
46+
47+
- name: install supervisor config
48+
file: state=link src={{ prefix }}/supervisor.conf dest=/etc/supervisor/conf.d/{{ slug }}.conf
49+
tags: full
50+
51+
- name: restart process
52+
supervisorctl: name="{{ slug }}" state=restarted
53+
tags: restart

pyproject.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[project]
2+
name = "{{ cookiecutter.project_name }}"
3+
version = "0.1.0"
4+
description = "{{ cookiecutter.description }}"
5+
authors = [
6+
{ name="{{ cookiecutter.author_name }}", email="{{ cookiecutter.author_email }}" },
7+
]
8+
requires-python = ">=3.13"
9+
dependencies = [
10+
"sentry-sdk",
11+
"django",
12+
"django-extensions",
13+
"dj-database-url",
14+
"python-decouple",
15+
"psycopg",
16+
]

supervisor.conf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[program:{{ cookiecutter.project_name }}]
2+
directory=/var/www/%(program_name)s
3+
command=uv --directory /var/www/%(program_name)s run -q bot.py
4+
stdout_logfile=/var/log/supervisor/%(program_name)s.log
5+
stderr_logfile=/var/log/supervisor/%(program_name)s.log
6+
user=deploy
7+
autostart=true
8+
autorestart=true

0 commit comments

Comments
 (0)