mirror of
https://github.com/hilman2/easy-ocpp.git
synced 2026-10-20 08:46:49 +02:00
Bestehende Installationen brachen beim Start mit dem Windows-Release-Binary ab: "migration 1 was previously applied but has been modified" – obwohl die Migration inhaltlich seit v0.2.0 unverändert ist. Ursache: .gitattributes enthielt nur "* text=auto". Zusammen mit core.autocrlf=true (Standard auf windows-latest-Runnern) checkt actions/checkout die .sql-Dateien als CRLF aus. sqlx::migrate! bettet den Dateiinhalt zur Compile-Zeit ein und bildet darüber eine SHA-384-Summe, die beim Start gegen _sqlx_migrations geprüft wird: 0001_initial.sql mit LF -> 0651a39f... 0001_initial.sql mit CRLF -> 90093a9a... Die GitHub-Windows-Builds v0.2.0 und v0.3.0 tragen die CRLF-Variante, lokal gebaute Exes und der Linux-Build die LF-Variante. Datenbanken, die mit einem LF-Binary angelegt wurden, kollidieren daher mit dem Windows-Release-ZIP. Zwei Änderungen: 1. .gitattributes erzwingt "*.sql text eol=lf". Ab jetzt erzeugen Windows- und Linux-Runner identische Prüfsummen. 2. db::repair_line_ending_checksums() hebt eine gespeicherte Prüfsumme vor dem Migrationslauf an – aber nur, wenn sie exakt der Zeilenenden-Variante desselben SQL entspricht, in beide Richtungen. Ohne diesen Schritt würde Punkt 1 jede Installation brechen, die mit dem CRLF-Binary aus dem GitHub-ZIP von v0.2.0/v0.3.0 angelegt wurde. Echte inhaltliche Abweichungen führen weiterhin zum Abbruch durch sqlx. sha2 wird dafür direkte Dependency (war bereits transitiv gelockt, die Auflösung ändert sich nicht). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
96 lines
2.8 KiB
Rust
96 lines
2.8 KiB
Rust
use std::net::SocketAddr;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use anyhow::{Context, Result};
|
|
use clap::Parser;
|
|
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
|
use sqlx::SqlitePool;
|
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
|
|
|
mod auth;
|
|
mod config;
|
|
mod db;
|
|
mod domain;
|
|
mod error;
|
|
mod i18n;
|
|
mod ocpp;
|
|
mod web;
|
|
|
|
pub use error::{AppError, AppResult};
|
|
|
|
#[derive(Clone)]
|
|
pub struct AppState {
|
|
pub db: SqlitePool,
|
|
pub ocpp_hub: Arc<ocpp::hub::Hub>,
|
|
pub config: Arc<config::Config>,
|
|
}
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(name = "easy-occp", about = "Wallbox-Management-Tool (OCPP)")]
|
|
struct Cli {
|
|
/// Pfad zur Konfigurationsdatei (TOML).
|
|
#[arg(long, default_value = "config.toml")]
|
|
config: PathBuf,
|
|
/// Admin-Passwort zurücksetzen (setzt auf das angegebene Passwort).
|
|
#[arg(long, value_name = "PASSWORT")]
|
|
reset_admin: Option<String>,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
tracing_subscriber::registry()
|
|
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,sqlx=warn")))
|
|
.with(fmt::layer().with_target(false))
|
|
.init();
|
|
|
|
let cli = Cli::parse();
|
|
let cfg = config::Config::load(&cli.config)?;
|
|
std::fs::create_dir_all(cfg.data_dir())
|
|
.with_context(|| format!("Konnte Datenverzeichnis {:?} nicht anlegen", cfg.data_dir()))?;
|
|
|
|
let db_url = format!("sqlite://{}", cfg.db_path().to_string_lossy().replace('\\', "/"));
|
|
tracing::info!("Datenbank: {}", db_url);
|
|
|
|
let opts = SqliteConnectOptions::new()
|
|
.filename(cfg.db_path())
|
|
.create_if_missing(true)
|
|
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
|
.busy_timeout(std::time::Duration::from_secs(5))
|
|
.foreign_keys(true);
|
|
|
|
let db = SqlitePoolOptions::new()
|
|
.max_connections(8)
|
|
.connect_with(opts)
|
|
.await
|
|
.context("SQLite-Pool konnte nicht geöffnet werden")?;
|
|
|
|
let migrator = sqlx::migrate!("./migrations");
|
|
db::repair_line_ending_checksums(&db, &migrator).await?;
|
|
migrator
|
|
.run(&db)
|
|
.await
|
|
.context("Migrationen fehlgeschlagen")?;
|
|
|
|
db::bootstrap_admin(&db, cli.reset_admin.as_deref()).await?;
|
|
|
|
let state = AppState {
|
|
db: db.clone(),
|
|
ocpp_hub: Arc::new(ocpp::hub::Hub::default()),
|
|
config: Arc::new(cfg.clone()),
|
|
};
|
|
|
|
let http_addr: SocketAddr = cfg.http.bind.parse().context("http.bind ungültig")?;
|
|
let app = web::router(state.clone());
|
|
|
|
tracing::info!("HTTP/UI hört auf http://{}", http_addr);
|
|
tracing::info!("OCPP-WebSocket unter ws://<host>:<port>/ocpp/<chargePointId>");
|
|
|
|
let listener = tokio::net::TcpListener::bind(http_addr).await?;
|
|
axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
|
|
.await
|
|
.context("HTTP-Server beendet")?;
|
|
|
|
Ok(())
|
|
}
|