From 607e4d758b90840a797bc684f3e8267f10e276ed Mon Sep 17 00:00:00 2001 From: byun Date: Tue, 26 May 2026 21:10:16 +0900 Subject: [PATCH] =?UTF-8?q?Steve/EV=20Dashboard/nginx=20=EB=8F=84=EC=BB=A4?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=B0=8F=20=EC=84=A4=EC=A0=95=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ev-charging-backend/.env | 35 ++++++++++ ev-charging-backend/Dockerfile | 15 +++++ ev-charging-backend/Dockerfile.proxy | 10 +++ ev-charging-backend/docker-compose.yml | 84 ++++++++++++++++++++++++ nginx/s1.byunc.com.conf | 89 ++++++++++++++++++++++++++ steve/Dockerfile | 25 ++++++++ steve/JettyConfiguration.java | 47 ++++++++++++++ steve/application-docker.properties | 66 +++++++++++++++++++ steve/docker-compose.yml | 31 +++++++++ 9 files changed, 402 insertions(+) create mode 100644 ev-charging-backend/.env create mode 100644 ev-charging-backend/Dockerfile create mode 100755 ev-charging-backend/Dockerfile.proxy create mode 100644 ev-charging-backend/docker-compose.yml create mode 100644 nginx/s1.byunc.com.conf create mode 100644 steve/Dockerfile create mode 100644 steve/JettyConfiguration.java create mode 100644 steve/application-docker.properties create mode 100644 steve/docker-compose.yml diff --git a/ev-charging-backend/.env b/ev-charging-backend/.env new file mode 100644 index 0000000..5f09019 --- /dev/null +++ b/ev-charging-backend/.env @@ -0,0 +1,35 @@ +# ── 데이터베이스 ── +POSTGRES_DB=ev_charging +POSTGRES_USER=evuser +POSTGRES_PASSWORD=evpass1234 +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 + +# ── Redis ── +REDIS_HOST=redis +REDIS_PORT=6379 + +# ── Steve OCPP 서버 ── +STEVE_BASE_URL=https://s1.byunc.com/steve +STEVE_API_USER=admin +STEVE_API_PASSWORD=changeme + +# ── 토스페이먼츠 ── +TOSS_CLIENT_KEY=test_ck_Poxy1XQL8RYmzR9JgL5lr7nO5Wml +TOSS_SECRET_KEY=test_sk_ZLKGPx4M3M90lvAvzx1n3BaWypv1 +#TOSS_CLIENT_KEY=test_ck_xxxxxxxxxx +#TOSS_SECRET_KEY=test_sk_xxxxxxxxxx + +# ── 요금 설정 (원/kWh) ── +ELECTRICITY_RATE=120 +SERVICE_MARGIN=50 + +# ── JWT ── +JWT_SECRET=your-super-secret-key-change-this +JWT_ALGORITHM=HS256 +JWT_EXPIRE_MINUTES=1440 + +# ── 서버 ── +SERVER_HOST=0.0.0.0 +SERVER_PORT=8000 +DEBUG=true diff --git a/ev-charging-backend/Dockerfile b/ev-charging-backend/Dockerfile new file mode 100644 index 0000000..675357a --- /dev/null +++ b/ev-charging-backend/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11-slim + +WORKDIR /code + +# 시스템 패키지 +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc libpq-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY ./app /code/app + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/ev-charging-backend/Dockerfile.proxy b/ev-charging-backend/Dockerfile.proxy new file mode 100755 index 0000000..df5c2f7 --- /dev/null +++ b/ev-charging-backend/Dockerfile.proxy @@ -0,0 +1,10 @@ +FROM python:3.11-slim + +WORKDIR /code + +RUN pip install --no-cache-dir websockets aiohttp + +COPY entrypoint-proxy.sh /code/ +RUN chmod +x /code/entrypoint-proxy.sh + +CMD ["/code/entrypoint-proxy.sh"] diff --git a/ev-charging-backend/docker-compose.yml b/ev-charging-backend/docker-compose.yml new file mode 100644 index 0000000..60ab9bc --- /dev/null +++ b/ev-charging-backend/docker-compose.yml @@ -0,0 +1,84 @@ +services: + # ── FastAPI 백엔드 ── + api: + build: . + container_name: ev-api + restart: unless-stopped + ports: + - "8000:8000" + env_file: + - .env + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - ./app:/code/app + - ./dashboard.html:/code/dashboard.html:ro + - ./simulator.html:/code/simulator.html:ro + networks: + - ev-net + + # ── OCPP 프록시 서버 ── + proxy: + build: + context: . + dockerfile: Dockerfile.proxy + container_name: ev-proxy + restart: unless-stopped + ports: + - "9002:9002" + - "9003:9003" + volumes: + - ./ocpp_proxy_server.py:/code/ocpp_proxy_server.py:ro + - ./proxy_control.html:/code/proxy_control.html:ro + - proxy_logs:/code/ocpp_logs + - proxy_config:/code/config + networks: + - ev-net + + # ── PostgreSQL ── + postgres: + image: postgres:16-alpine + container_name: ev-postgres + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-ev_charging} + POSTGRES_USER: ${POSTGRES_USER:-evuser} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-evpass1234} + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-evuser}"] + interval: 5s + timeout: 3s + retries: 5 + networks: + - ev-net + + # ── Redis ── + redis: + image: redis:7-alpine + container_name: ev-redis + restart: unless-stopped + ports: + - "6375:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + networks: + - ev-net + +volumes: + pgdata: + proxy_logs: + proxy_config: + +networks: + ev-net: + driver: bridge diff --git a/nginx/s1.byunc.com.conf b/nginx/s1.byunc.com.conf new file mode 100644 index 0000000..d5cac22 --- /dev/null +++ b/nginx/s1.byunc.com.conf @@ -0,0 +1,89 @@ +server { + #listen [::]:80; + #root /var/www/html/mysite.com; + index index.php index.html index.htm; + server_name s1.byunc.com; + + #error_log /var/log/nginx/mysite.com_error.log; + #access_log /var/log/nginx/mysite.com_access.log; + + client_max_body_size 16G; + + location = / { + return 302 /steve/; + } + + location /steve { + proxy_pass http://192.168.0.126:8180; + proxy_set_header Host $host; + 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 $scheme; + } + + +# ── FastAPI 대시보드 + API ── + location = /dashboard { + proxy_pass http://192.168.0.126:8000; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /api/ { + proxy_pass http://192.168.0.126:8000; + proxy_set_header Host $host; + 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 $scheme; + } + + location /health { + proxy_pass http://192.168.0.126:8000; + } + + # WebSocket 전용 설정 (핵심) + location /steve/websocket { + proxy_pass http://192.168.0.126:8180; + + # WebSocket 업그레이드 헤더 + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + + # WebSocket 타임아웃 (충전 중 끊기지 않게) + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_connect_timeout 86400s; + } + location / { + proxy_pass http://192.168.0.126:8180; + proxy_set_header Host $host; + 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 $scheme; + proxy_redirect off; + proxy_buffering off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_connect_timeout 86400s; + } + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/s1.byunc.com/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/s1.byunc.com/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + +} +server { + if ($host = s1.byunc.com) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + server_name s1.byunc.com; + listen 80; + return 404; # managed by Certbot + + +} diff --git a/steve/Dockerfile b/steve/Dockerfile new file mode 100644 index 0000000..b03465a --- /dev/null +++ b/steve/Dockerfile @@ -0,0 +1,25 @@ +FROM eclipse-temurin:21-jdk + +ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 + +MAINTAINER Ling Li + +# Download and install dockerize. +# Needed so the web container will wait for MariaDB to start. +ENV DOCKERIZE_VERSION v0.19.0 +RUN curl -sfL https://github.com/powerman/dockerize/releases/download/"$DOCKERIZE_VERSION"/dockerize-`uname -s`-`uname -m` | install /dev/stdin /usr/local/bin/dockerize + +EXPOSE 8180 +EXPOSE 8443 +WORKDIR /code + +VOLUME ["/code"] + +# Copy the application's code +COPY . /code + +# Wait for the db to startup(via dockerize), then +# Build and run steve, requires a db to be available on port 3306 +CMD dockerize -wait tcp://mariadb:3306 -timeout 60s && \ + ./mvnw clean package -Pdocker,mariadb -Djdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2" && \ + java -XX:MaxRAMPercentage=85 -jar target/steve.war diff --git a/steve/JettyConfiguration.java b/steve/JettyConfiguration.java new file mode 100644 index 0000000..050a7e9 --- /dev/null +++ b/steve/JettyConfiguration.java @@ -0,0 +1,47 @@ +/* + * SteVe - SteckdosenVerwaltung - https://github.com/steve-community/steve + * Copyright (C) 2013-2026 SteVe Community Team + * All Rights Reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package de.rwth.idsg.steve.config; + +import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.ServerConnector; +import org.springframework.boot.jetty.JettyServerCustomizer; +import org.springframework.boot.jetty.servlet.JettyServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JettyConfiguration { + + @Bean + public WebServerFactoryCustomizer jettyHttpComplianceCustomizer() { + return factory -> factory.addServerCustomizers((JettyServerCustomizer) server -> { + for (var connector : server.getConnectors()) { + if (connector instanceof ServerConnector serverConnector) { + for (var cf : serverConnector.getConnectionFactories()) { + if (cf instanceof HttpConnectionFactory httpCF) { + httpCF.getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC7230_LEGACY); + } + } + } + } + }); + } +} diff --git a/steve/application-docker.properties b/steve/application-docker.properties new file mode 100644 index 0000000..0417a1d --- /dev/null +++ b/steve/application-docker.properties @@ -0,0 +1,66 @@ +# Just to be backwards compatible with previous versions, this is set to "steve", +# since there might be already configured chargepoints expecting the older path. +# Otherwise, might as well be changed to something else or be left empty. +# +context.path = steve + +# Database configuration +# +db.ip = mariadb +db.port = 3306 +db.schema = stevedb +db.user = steve +db.password = changeme + +# Credentials for Web interface access +# +auth.user = admin +auth.password = 1234 + +# The header key and value for Web API access using API key authorization. +# Both must be set for Web APIs to be enabled. Otherwise, we will block all calls. +# +webapi.key = STEVE-API-KEY +webapi.value = + +# Jetty configuration +# +server.host = 0.0.0.0 +server.gzip.enabled = false + +# Jetty HTTP configuration +# +http.enabled = true +http.port = 8180 + +# Jetty HTTPS configuration +# +https.enabled = false +https.port = 8443 +keystore.path = +keystore.password = + +# When the WebSocket/Json charge point opens more than one WebSocket connection, +# we need a mechanism/strategy to select one of them for outgoing requests. +# For allowed values see de.rwth.idsg.steve.ocpp.ws.custom.WsSessionSelectStrategyEnum. +# +ws.session.select.strategy = ALWAYS_LAST + +# if BootNotification messages arrive (SOAP) or WebSocket connection attempts are made (JSON) from unknown charging +# stations, we reject these charging stations, because stations with these chargeBoxIds were NOT inserted into database +# beforehand. by setting this property to true, this behaviour can be modified to automatically insert unknown +# stations into database and accept their requests. +# +# CAUTION: setting this property to true is very dangerous, because we will accept EVERY BootNotification or WebSocket +# connection attempt from ANY sender as long as the sender knows the URL and sends a valid message. +# +auto.register.unknown.stations = false + +# if this field is set, it will take precedence over the default regex we are using in +# de.rwth.idsg.steve.web.validation.ChargeBoxIdValidator.REGEX to validate the format of the chargeBoxId values +# +charge-box-id.validation.regex = + +### DO NOT MODIFY ### +db.sql.logging = false +profile = prod diff --git a/steve/docker-compose.yml b/steve/docker-compose.yml new file mode 100644 index 0000000..8f0e1e0 --- /dev/null +++ b/steve/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.0" + +volumes: + db-data: + external: false + +services: + + db: + image: mariadb:10.11.16 + restart: unless-stopped + ports: + - 3307:3306 + environment: + MYSQL_RANDOM_ROOT_PASSWORD: "yes" + MYSQL_DATABASE: stevedb + MYSQL_USER: steve + MYSQL_PASSWORD: changeme + app: + restart: unless-stopped + build: . + links: + - "db:mariadb" + volumes: + - .:/code + ports: + - "8180:8180" + - "8445:8443" + depends_on: + - db +