first commit

This commit is contained in:
NORBOYEVSAMARIDDIN
2026-02-07 11:18:38 +05:00
commit 493cb58222
228 changed files with 10859 additions and 0 deletions

3
resources/layout/.flake8 Normal file
View File

@@ -0,0 +1,3 @@
[flake8]
max-line-length = 120
ignore = E701, E704, W503

View File

@@ -0,0 +1,19 @@
FROM python:3.13-alpine
ENV PYTHONPYCACHEPREFIX=/dev/null
ENV UV_CACHE_DIR=/root/.cache/uv
ENV UV_LINK_MODE=copy
ENV VENV_PATH=/opt/venv
RUN apk update && apk add --no-cache git gettext curl netcat-openbsd
WORKDIR /code
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.cargo/bin:$VENV_PATH/bin:/root/.local/bin:$PATH"
COPY requirements.txt /code/requirements.txt
RUN uv venv $VENV_PATH
RUN --mount=type=cache,target=/root/.cache/uv uv pip install -r requirements.txt
CMD ["sh", "./entrypoint.sh"]

View File

@@ -0,0 +1,3 @@
FROM nginx:alpine
COPY ./resources/layout/nginx.conf /etc/nginx/nginx.conf

188
resources/layout/Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,188 @@
pipeline {
agent any
environment {
PROD_ENV = "/opt/env/.env.aparat"
IMAGE_NAME = "aparat"
TEST_TAG = "test"
PROD_TAG = "latest"
CONTAINER_DB = "aparat_db_test"
CONTAINER_WEB = "aparat_web_test"
CONTAINER_REDIS = "aparat_redis_test"
STACK_NAME = "aparat"
}
stages {
stage('Check Commit Message') {
steps {
script {
def commitMsg = sh(
script: "git log -1 --pretty=%B",
returnStdout: true
).trim()
if (commitMsg.contains("[ci skip]")) {
echo "Commit message contains [ci skip], aborting pipeline 🚫"
currentBuild.result = 'ABORTED'
error("Pipeline aborted because of [ci skip]")
}
}
}
}
stage('Checkout Code') {
steps {
git branch: 'main', credentialsId: 'ssh', url: 'git@github.com:JscorpTech/aparat.git'
}
}
stage('Prepare') {
steps {
script {
env.GIT_MESSAGE = sh(
script: "git log -1 --pretty=%B ${env.GIT_COMMIT}",
returnStdout: true
).trim()
}
}
}
stage("Update files") {
when {
expression { currentBuild.currentResult == "SUCCESS" }
}
steps {
withCredentials([usernamePassword(credentialsId: 'dockerhub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh """
sed -i 's|image: ${DOCKER_USER}/${IMAGE_NAME}:.*|image: ${DOCKER_USER}/${IMAGE_NAME}:${BUILD_NUMBER}|' stack.yaml
sed -i 's/return HttpResponse("OK.*"/return HttpResponse("OK: #${GIT_COMMIT}"/' config/urls.py
"""
// git config --global user.email "admin@jscorp.uz"
// git config --global user.name "Jenkins"
// if ! git diff --quiet stack.yaml; then
// git add stack.yaml
// git commit -m "feat(swarm) Update image tag to ${BUILD_NUMBER} [ci skip]"
// git push origin main
// else
// echo "No changes in stack.yaml"
// fi
}
}
}
stage('Build Image') {
steps {
sh '''
if [ -e ${PROD_ENV} ]; then
echo env exists
else
mkdir -p $(dirname ${PROD_ENV})
cp ./.env.example ${PROD_ENV}
fi
cp ${PROD_ENV} ./.env
'''
sh """
docker build -t ${IMAGE_NAME}:${PROD_TAG} --build-arg SCRIPT=entrypoint-server.sh -f ./docker/Dockerfile.web .
"""
}
}
stage('Start Test DB') {
steps {
sh """
docker run -d --rm --name ${CONTAINER_DB} -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=testdb postgres:16
docker run -d --rm --name ${CONTAINER_REDIS} redis
echo "⏳ Waiting for database..."
for i in {1..30}; do
if docker exec ${CONTAINER_DB} pg_isready -U postgres >/dev/null 2>&1; then
echo "✅ Database ready"
break
fi
echo "Database not ready yet... retrying..."
sleep 2
done
"""
}
}
stage('Run Migrations & Tests') {
steps {
sh """
docker run --rm --name ${CONTAINER_WEB} --link ${CONTAINER_DB}:db --link ${CONTAINER_REDIS}:redis \
-e DB_HOST=db \
-e DB_PORT=5432 \
-e DB_NAME=testdb \
-e DB_USER=postgres \
-e DB_PASSWORD=postgres \
-e DJANGO_SETTINGS_MODULE=config.settings.test \
${IMAGE_NAME}:${PROD_TAG} \
sh -c "python manage.py migrate && pytest -v"
"""
}
}
stage('Publish to DockerHub') {
when {
expression { currentBuild.currentResult == "SUCCESS" }
}
steps {
withCredentials([usernamePassword(credentialsId: 'dockerhub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh '''
echo "${DOCKER_PASS}" | docker login -u "${DOCKER_USER}" --password-stdin
docker tag ${IMAGE_NAME}:${PROD_TAG} ${DOCKER_USER}/${IMAGE_NAME}:${BUILD_NUMBER}
docker tag ${IMAGE_NAME}:${PROD_TAG} ${DOCKER_USER}/${IMAGE_NAME}:${PROD_TAG}
docker push ${DOCKER_USER}/${IMAGE_NAME}:${BUILD_NUMBER}
docker push ${DOCKER_USER}/${IMAGE_NAME}:${PROD_TAG}
'''
}
}
}
stage('Deploy stack') {
when {
expression { currentBuild.currentResult == "SUCCESS" }
}
steps {
withCredentials([usernamePassword(credentialsId: 'dockerhub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh '''
docker stack deploy -c stack.yaml ${STACK_NAME}
'''
}
}
}
}
post {
always {
sh """
docker stop ${CONTAINER_DB} || true
docker stop ${CONTAINER_REDIS} || true
"""
echo "Pipeline finished: ${currentBuild.currentResult}"
}
success {
withCredentials([
string(credentialsId: 'bot-token', variable: 'BOT_TOKEN'),
string(credentialsId: 'chat-id', variable: 'CHAT_ID')
]) {
sh '''
curl -s -X POST https://api.telegram.org/bot${BOT_TOKEN}/sendMessage \
-d chat_id=${CHAT_ID} \
-d text="✅ SUCCESS: ${JOB_NAME} #${BUILD_NUMBER}\nRepo: ${GIT_URL}\nBranch: ${GIT_BRANCH}\nCommit: ${GIT_COMMIT}\nMessage: ${GIT_MESSAGE}"
'''
}
}
failure {
withCredentials([
string(credentialsId: 'bot-token', variable: 'BOT_TOKEN'),
string(credentialsId: 'chat-id', variable: 'CHAT_ID')
]) {
sh '''
curl -s -X POST https://api.telegram.org/bot${BOT_TOKEN}/sendMessage \
-d chat_id=${CHAT_ID} \
-d text="🚨 FAILED: ${JOB_NAME} #${BUILD_NUMBER}\nRepo: ${GIT_URL}\nBranch: ${GIT_BRANCH}\nCommit: ${GIT_COMMIT}\nMessage: ${GIT_MESSAGE}"
'''
}
}
}
}

View File

@@ -0,0 +1,61 @@
networks:
aparat:
driver: bridge
volumes:
pg_data: null
pycache: null
media: null
static: null
services:
nginx:
networks:
- aparat
ports:
- ${PORT:-8001}:80
volumes:
- ./resources/layout/nginx.conf:/etc/nginx/nginx.conf
- media:/usr/share/nginx/html/resources/media/:ro
- static:/usr/share/nginx/html/resources/staticfiles/:ro
build:
context: .
dockerfile: ./docker/Dockerfile.nginx
depends_on:
- web
web:
networks:
- aparat
build:
context: .
dockerfile: ./docker/Dockerfile.web
restart: always
env_file:
- .env
environment:
- PYTHONPYCACHEPREFIX=/var/cache/pycache
- SCRIPT=${SCRIPT:-entrypoint.sh}
volumes:
- media:/code/resources/media/
- static:/code/resources/staticfiles/
- pycache:/var/cache/pycache
depends_on:
- db
- redis
db:
networks:
- aparat
image: postgres:16
restart: always
environment:
POSTGRES_DB: ${DB_NAME:-django}
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:?Database password must be set in .env file}
volumes:
- pg_data:/var/lib/postgresql/data
redis:
networks:
- aparat
restart: always
image: redis

View File

@@ -0,0 +1,46 @@
networks:
aparat:
driver: bridge
volumes:
pg_data: null
pycache: null
services:
web:
env_file:
- .env
networks:
- aparat
build:
context: .
dockerfile: ./docker/Dockerfile.web
restart: always
container_name: test_web
environment:
- PYTHONPYCACHEPREFIX=/var/cache/pycache
- SCRIPT=${SCRIPT:-entrypoint.sh}
volumes:
- pycache:/var/cache/pycache
depends_on:
- db
- redis
db:
networks:
- aparat
image: postgres:16
restart: always
container_name: test_db
environment:
POSTGRES_DB: ${DB_NAME:-django}
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-2309}
volumes:
- pg_data:/var/lib/postgresql/data
redis:
container_name: test_redis
networks:
- aparat
restart: always
image: redis

View File

@@ -0,0 +1,60 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
nginx.conf: |
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# Logging settings (optional)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 1024M;
# Server block for handling requests
server {
listen 80;
server_name _;
location / {
proxy_pass http://django:8000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
}
location /ws/ {
proxy_pass http://django:8000; # Uvicorn serveri ishga tushadigan port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $http_host;
}
location /resources/static/ {
alias /usr/share/nginx/html/resources/staticfiles/;
}
location /resources/media/ {
alias /usr/share/nginx/html/resources/media/;
}
}
}

View File

@@ -0,0 +1,33 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: db
image: postgres:16
env:
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: "2309"
- name: POSTGRES_DB
value: django
ports:
- containerPort: 5432
volumeMounts:
- name: db
mountPath: /var/lib/postgresql/data
volumes:
- name: db
persistentVolumeClaim:
claimName: db

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: db
spec:
type: ClusterIP
selector:
app: db
ports:
- port: 5432
targetPort: 5432

View File

@@ -0,0 +1,33 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: django
spec:
replicas: 1
selector:
matchLabels:
app: django
template:
metadata:
labels:
app: django
spec:
containers:
- name: django
image: "2.0"
ports:
- containerPort: 8000
volumeMounts:
- name: assets
mountPath: /code/resources/staticfiles
- name: media
mountPath: /code/resources/media
volumes:
- name: assets
persistentVolumeClaim:
claimName: assets
- name: media
persistentVolumeClaim:
claimName: media

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: django
spec:
type: ClusterIP
selector:
app: django
ports:
- port: 8000
targetPort: 8000

View File

@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config-volume
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
- name: assets
mountPath: /usr/share/nginx/html/resources/staticfiles
readOnly: true
- name: media
mountPath: /usr/share/nginx/html/resources/media
readOnly: true
volumes:
- name: assets
persistentVolumeClaim:
claimName: assets
- name: media
persistentVolumeClaim:
claimName: media
- name: nginx-config-volume
configMap:
name: nginx-config

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30000

View File

@@ -0,0 +1,35 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: assets
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: media
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 10Gi

View File

@@ -0,0 +1,5 @@
[mypy]
check_untyped_defs = True
[mypy-requests.*]
ignore_missing_imports = True

View File

@@ -0,0 +1,54 @@
# Main configuration block
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# Logging settings (optional)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 1024M;
# Server block for handling requests
server {
listen 80;
server_name _;
location / {
proxy_pass http://web:8000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
}
location /ws/ {
proxy_pass http://web:8000; # Uvicorn serveri ishga tushadigan port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $http_host;
}
location /resources/static/ {
alias /usr/share/nginx/html/resources/staticfiles/;
}
location /resources/media/ {
alias /usr/share/nginx/html/resources/media/;
}
}
}