Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.git
.gitignore
.venv
__pycache__
*.pyc
*.pyo
*.pyd
.pytest_cache
.mypy_cache
node_modules
.env
README.md
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Static asset version for cache busting (bump on each deploy)
ASSET_VERSION=1

# Optional root path if app is served under a subpath, e.g. /portfolio
ROOT_PATH=

# Comma-separated allowed hosts for TrustedHostMiddleware
# Example: example.com,www.example.com,localhost,127.0.0.1
ALLOWED_HOSTS=*
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
FROM python:3.11-slim

WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

COPY requirements.txt .
WORKDIR /app

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

COPY . .

RUN useradd --create-home --shell /bin/bash appuser && \
chown -R appuser:appuser /app
USER appuser

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
38 changes: 31 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
- Docker / docker-compose

## 1) Быстрый запуск (Docker)
1. Создай `.env` в корне (опционально):
```env
APP_ENV=production
1. Скопируй пример env:
```bash
cp .env.example .env
```
2. Подними контейнер:
```bash
docker compose up --build
docker compose up --build -d
```
3. Открой сайт: `http://localhost:8000`

Expand All @@ -28,7 +28,27 @@
uvicorn app.main:app --reload
```

## 3) Проверки
## 3) Минимум для деплоя (production-ready для портфолио)
- Непривилегированный пользователь в контейнере (`appuser`).
- Health/readiness endpoint'ы: `/healthz`, `/readyz`.
- `docker-compose` healthcheck (проверка `/readyz`).
- Базовые security headers + gzip.
- Ограничение хостов через `ALLOWED_HOSTS`.
- Версионирование ассетов через `ASSET_VERSION`.

## 4) Переменные окружения
- `ASSET_VERSION` — версия статики для cache-busting (повышай при релизе).
- `ROOT_PATH` — если приложение работает не с `/`, а с подпути.
- `ALLOWED_HOSTS` — список разрешённых хостов через запятую.

Пример:
```env
ASSET_VERSION=5
ROOT_PATH=
ALLOWED_HOSTS=example.com,www.example.com
```

## 5) Проверки
- Проверка синтаксиса Python:
```bash
python -m compileall app static/js
Expand All @@ -37,8 +57,12 @@
```bash
curl -s http://localhost:8000/healthz
```
- Проверка readiness endpoint:
```bash
curl -s http://localhost:8000/readyz
```

## 4) Структура проекта
- `app/` — backend-код (роуты)
## 6) Структура проекта
- `app/` — backend-код (роуты и middleware)
- `templates/` — Jinja-шаблоны страниц
- `static/` — CSS/JS/контент (`static/content/*.json`)
58 changes: 52 additions & 6 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,84 @@
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from starlette.middleware.gzip import GZipMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware

app = FastAPI(title='Personal Site')

def parse_allowed_hosts(raw: str) -> list[str]:
hosts = [h.strip() for h in raw.split(',') if h.strip()]
return hosts or ['*']


def security_headers() -> dict[str, str]:
# CSP intentionally omitted because current templates use inline onclick handlers.
# Adding CSP without refactoring handlers to JS listeners would break UI controls.
return {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()'
}


ROOT_PATH = os.getenv('ROOT_PATH', '')
ASSET_VERSION = os.getenv('ASSET_VERSION', '1')
ALLOWED_HOSTS = parse_allowed_hosts(os.getenv('ALLOWED_HOSTS', '*'))

app = FastAPI(title='Personal Site', root_path=ROOT_PATH)
templates = Jinja2Templates(directory='templates')

app.add_middleware(GZipMiddleware, minimum_size=1024)
app.add_middleware(TrustedHostMiddleware, allowed_hosts=ALLOWED_HOSTS)
app.mount('/static', StaticFiles(directory='static'), name='static')


@app.middleware('http')
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
for header, value in security_headers().items():
response.headers.setdefault(header, value)
return response


def render_template(request: Request, template_name: str, **context):
page_context = {'request': request, 'asset_version': ASSET_VERSION}
page_context.update(context)
return templates.TemplateResponse(template_name, page_context)


@app.get('/healthz', name='healthz')
def healthz():
return {'status': 'ok'}


@app.get('/readyz', name='readyz')
def readyz():
return JSONResponse({'status': 'ready'})


@app.get('/', name='index')
def index(request: Request):
return templates.TemplateResponse('index.html', {'request': request})
return render_template(request, 'index.html')


@app.get('/skills', name='skills')
def skills(request: Request):
return templates.TemplateResponse('skills.html', {'request': request})
return render_template(request, 'skills.html')


@app.get('/projects', name='projects')
def projects(request: Request):
return templates.TemplateResponse('projects.html', {'request': request})
return render_template(request, 'projects.html')


@app.get('/projects/{project_id}', name='project_details')
def project_details(request: Request, project_id: str):
return templates.TemplateResponse('project.html', {'request': request, 'project_id': project_id})
return render_template(request, 'project.html', project_id=project_id)


@app.get('/about', name='about')
def about(request: Request):
return templates.TemplateResponse('about.html', {'request': request})
return render_template(request, 'about.html')
15 changes: 13 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@ services:
build:
context: .
container_name: personal_site_web
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- .env
volumes:
- .:/app
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/readyz', timeout=3)"
]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
4 changes: 0 additions & 4 deletions static/assets/industrial_icon/automation.svg

This file was deleted.

5 changes: 0 additions & 5 deletions static/assets/industrial_icon/hmi.svg

This file was deleted.

5 changes: 0 additions & 5 deletions static/assets/industrial_icon/plc.svg

This file was deleted.

5 changes: 0 additions & 5 deletions static/assets/web_icon/api.svg

This file was deleted.

7 changes: 0 additions & 7 deletions static/assets/web_icon/browser.svg

This file was deleted.

5 changes: 0 additions & 5 deletions static/assets/web_icon/code.svg

This file was deleted.

34 changes: 22 additions & 12 deletions static/content/en.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"hero_title": "Full-Stack Web Developer & Automation Engineer",
"hero_subtitle": "I design and build reliable web systems and production automation solutions — from backend architecture to industrial control integration.",
"hero_title": "Junior Backend Developer (Python)",
"hero_subtitle": "Junior Backend Developer (Python) with an engineering background in industrial automation.",
"projects_title": "Projects",
"hero_desc_title": "Short intro block",
"hero_description": "I work at the intersection of software engineering and industrial automation. My focus is building scalable web applications and integrating them with real-world production systems. I value clean architecture, predictable system behavior, and solutions that remain maintainable under growth.",
"hero_desc_title": "Automation engineer and backend-focused developer",
"hero_description": "Automation engineer with hands-on experience working with industrial equipment, including PLC (Rockwell Automation), HMI development (Weintek), and electrical equipment maintenance.",
"projects": [
{
"id": "plc-monitoring",
Expand All @@ -21,7 +21,7 @@
}
],
"about_title": "About Me",
"about_intro": "I am a full-stack engineer with a strong backend focus, working primarily with Python and modern web technologies.",
"about_intro": "I am an industrial automation engineer by profession. I work with industrial equipment, program PLCs (Rockwell Automation), develop HMI interfaces (Weintek, Rockwell Automation), and handle maintenance and repair of electrical systems.",
"about_skills_title": "Key skills",
"skills_backend_title": "Backend",
"skills_backend": [
Expand All @@ -47,13 +47,13 @@
"• HMI"
],
"about_highlights": [
"REST API design and implementation",
"Database architecture and optimization",
"Containerized environments (Docker)",
"Integration of web systems with industrial control environments"
"PLC (Rockwell Automation) and HMI (Weintek, Rockwell Automation)",
"Backend with Python (FastAPI), APIs, and Telegram bots",
"VPS deployments with Docker and Nginx",
"Practical mindset and integration-focused projects"
],
"about_background": "With a background in automation engineering, I understand how software interacts with physical processes. This allows me to design systems that are not only functional but also reliable in real production conditions.",
"about_growth": "I continuously improve my skills in scalable system design and infrastructure automation.",
"about_background": "I moved into web development intentionally and currently focus on backend development. I work with Python (FastAPI), build APIs, develop Telegram bots, and create small backend services. I also deploy applications on VPS using Docker and Nginx.",
"about_growth": "Currently looking for a junior backend developer position and continuing to improve my skills through hands-on projects.",
"nav_home": "Home",
"nav_skills": "Skills",
"nav_projects": "Projects",
Expand All @@ -62,5 +62,15 @@
"contact_gmail": "Gmail",
"contact_copied": "Email copied to clipboard",
"contact_telegram": "Telegram",
"contact_github": "GitHub"
"contact_github": "GitHub",
"hero_description_1": "Automation engineer with hands-on experience working with industrial equipment, including PLC (Rockwell Automation), HMI development (Weintek), and electrical equipment maintenance.",
"hero_description_2": "Currently focused on backend development: building services in Python (FastAPI), designing REST APIs, working with Telegram bots, and integrating with external services. Experienced in deploying applications on VPS using Docker and Nginx.",
"hero_description_3": "Also familiar with frontend technologies (HTML, CSS, JavaScript), which helps to understand the full development cycle and interaction between client and server sides.",
"hero_description_4": "Actively improving backend development skills and looking for a junior backend developer position.",
"about_p1": "I am an industrial automation engineer by profession. I work with industrial equipment, program PLCs (Rockwell Automation), develop HMI interfaces (Weintek, Rockwell Automation), and handle maintenance and repair of electrical systems.",
"about_p2": "This experience gives me a clear understanding of how software operates in real-world environments, where reliability, predictability, and ease of maintenance are critical.",
"about_p3": "I moved into web development intentionally and currently focus on backend development. I work with Python (FastAPI), build APIs, develop Telegram bots, and create small backend services. I also deploy applications on VPS using Docker and Nginx.",
"about_p4": "In both learning and development, I actively use AI tools to speed up workflows, troubleshoot issues, and explore solutions, while making sure I understand the code and how it works.",
"about_p5": "I prefer a practical approach: breaking down problems, delivering working solutions, and understanding how systems work under the hood. I am particularly interested in automation, integrations, and systems that connect different components.",
"about_p6": "Currently looking for a junior backend developer position and continuing to improve my skills through hands-on projects."
}
Loading