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
+