add k8s support and upload image to ghcr.io
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
.venv
|
||||||
|
venv
|
||||||
|
ENV
|
||||||
|
__pycache__
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
stats.db
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
|
|
||||||
|
.pytest_cache
|
||||||
|
.mypy_cache
|
||||||
|
.ruff_cache
|
||||||
|
|
||||||
|
run.sh
|
||||||
|
docker-compose*.yml
|
||||||
|
docker-compose*.yaml
|
||||||
@@ -3,4 +3,6 @@ MARZBAN_URL=https://your-marzban.example.com
|
|||||||
MARZBAN_USERNAME=
|
MARZBAN_USERNAME=
|
||||||
MARZBAN_PASSWORD=
|
MARZBAN_PASSWORD=
|
||||||
ADMIN_IDS=12345678,87654321
|
ADMIN_IDS=12345678,87654321
|
||||||
|
# Optional; remove/comment this line when no proxy is required.
|
||||||
|
# HTTP_PROXY=http://shared-http-proxy.proxy.svc.cluster.local:3128
|
||||||
DB_PATH=stats.db
|
DB_PATH=stats.db
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
name: Publish container
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate image metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/kr0sh512/tt-simple
|
||||||
|
tags: |
|
||||||
|
type=ref,event=tag
|
||||||
|
type=sha,prefix=sha-
|
||||||
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
|
||||||
|
- name: Build and publish image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
+21
-6
@@ -1,11 +1,26 @@
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim-bookworm
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY requirements.txt .
|
RUN groupadd --gid 1000 app \
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
&& useradd \
|
||||||
|
--uid 1000 \
|
||||||
|
--gid 1000 \
|
||||||
|
--no-create-home \
|
||||||
|
--shell /usr/sbin/nologin \
|
||||||
|
app
|
||||||
|
|
||||||
COPY bot.py .
|
COPY requirements.txt ./
|
||||||
COPY .env .
|
RUN pip install \
|
||||||
|
--no-cache-dir \
|
||||||
|
--disable-pip-version-check \
|
||||||
|
-r requirements.txt
|
||||||
|
|
||||||
CMD sh -c "export $(grep -v '^#' .env | xargs) && python bot.py"
|
COPY --chown=app:app bot.py ./
|
||||||
|
|
||||||
|
USER app:app
|
||||||
|
|
||||||
|
CMD ["python", "bot.py"]
|
||||||
|
|||||||
@@ -42,10 +42,105 @@ cp .env.example .env
|
|||||||
|
|
||||||
Fill `.env` and run:
|
Fill `.env` and run:
|
||||||
```bash
|
```bash
|
||||||
export $(grep -v '^#' .env | xargs)
|
set -a
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
python bot.py
|
python bot.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Container image
|
||||||
|
|
||||||
|
The image contains only the application and Python dependencies. Runtime secrets
|
||||||
|
and the SQLite database are deliberately excluded.
|
||||||
|
|
||||||
|
Build and verify it locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build --pull -t tt-simple:dev .
|
||||||
|
|
||||||
|
docker run --rm --entrypoint sh tt-simple:dev -c \
|
||||||
|
'test ! -e /app/.env && test ! -e /app/stats.db && test -e /app/bot.py'
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it with environment variables and persistent SQLite storage:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker volume create tt-simple-data
|
||||||
|
|
||||||
|
docker run --rm \
|
||||||
|
--env-file .env \
|
||||||
|
--env DB_PATH=/data/stats.db \
|
||||||
|
--volume tt-simple-data:/data \
|
||||||
|
tt-simple:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publishing to GHCR
|
||||||
|
|
||||||
|
`.github/workflows/container.yml` publishes a multi-architecture image to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ghcr.io/kr0sh512/tt-simple
|
||||||
|
```
|
||||||
|
|
||||||
|
A push to `main` publishes `latest` and `sha-<commit>` tags. A Git tag such as
|
||||||
|
`v0.1.0` publishes the matching version tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag v0.1.0
|
||||||
|
git push origin main v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### K3s deployment
|
||||||
|
|
||||||
|
Create the namespace first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f k8s/namespace.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Create or update the application Secret from the local `.env` file. The Secret
|
||||||
|
is never stored in Git:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n tt-simple create secret generic tt-simple-env \
|
||||||
|
--from-env-file=.env \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
For a private GHCR package, create a classic GitHub token with `read:packages`
|
||||||
|
and create the registry pull secret:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
read -rsp "GHCR token: " GHCR_TOKEN
|
||||||
|
echo
|
||||||
|
kubectl -n tt-simple create secret docker-registry ghcr-creds \
|
||||||
|
--docker-server=ghcr.io \
|
||||||
|
--docker-username=kr0sh512 \
|
||||||
|
--docker-password="$GHCR_TOKEN" \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
unset GHCR_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
If the package is public, remove `imagePullSecrets` from
|
||||||
|
`k8s/tt-simple.yaml`. Deploy the PVC and bot after publishing the `v0.1.0`
|
||||||
|
image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f k8s/tt-simple.yaml
|
||||||
|
kubectl -n tt-simple rollout status deployment/tt-simple
|
||||||
|
kubectl -n tt-simple logs -f deployment/tt-simple
|
||||||
|
```
|
||||||
|
|
||||||
|
The manifest injects the Secret, mounts persistent storage at `/data`, and sets
|
||||||
|
`DB_PATH=/data/stats.db`. To use the shared non-Russian proxy, add this to the
|
||||||
|
local `.env` before updating `tt-simple-env`:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
HTTP_PROXY=http://shared-http-proxy.proxy.svc.cluster.local:3128
|
||||||
|
```
|
||||||
|
|
||||||
|
The included namespace and pod labels satisfy the proxy `NetworkPolicy`.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
- Telegram Stars invoices use `currency="XTR"` and empty `provider_token`.
|
- Telegram Stars invoices use `currency="XTR"` and empty `provider_token`.
|
||||||
- Local SQLite database stores only payment statistics. User state is read from Marzban API.
|
- Local SQLite database stores only payment statistics. User state is read from Marzban API.
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: tt-simple
|
||||||
|
labels:
|
||||||
|
# Required by the shared proxy NetworkPolicy when HTTP_PROXY is enabled.
|
||||||
|
proxy-access: "true"
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: tt-simple-data
|
||||||
|
namespace: tt-simple
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 256Mi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: tt-simple
|
||||||
|
namespace: tt-simple
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: tt-simple
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
revisionHistoryLimit: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: tt-simple
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: tt-simple
|
||||||
|
# Required by the shared proxy NetworkPolicy when HTTP_PROXY is enabled.
|
||||||
|
proxy-client: "true"
|
||||||
|
spec:
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: ghcr-creds
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
fsGroup: 1000
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
containers:
|
||||||
|
- name: bot
|
||||||
|
# Replace this with an immutable release tag or image digest.
|
||||||
|
image: ghcr.io/kr0sh512/tt-simple:v0.1.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: tt-simple-env
|
||||||
|
env:
|
||||||
|
- name: DB_PATH
|
||||||
|
value: /data/stats.db
|
||||||
|
- name: PYTHONUNBUFFERED
|
||||||
|
value: "1"
|
||||||
|
- name: PYTHONDONTWRITEBYTECODE
|
||||||
|
value: "1"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 25m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 256Mi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: tt-simple-data
|
||||||
|
- name: tmp
|
||||||
|
emptyDir:
|
||||||
|
sizeLimit: 32Mi
|
||||||
Reference in New Issue
Block a user