init
This commit is contained in:
commit
55e9cf1f9c
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/target
|
||||||
|
.direnv
|
||||||
|
nixos.qcow2
|
||||||
|
result
|
1494
Cargo.lock
generated
Normal file
1494
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "made-you-look"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
askama = { version = "0.12.1", features = ["with-axum"] }
|
||||||
|
askama_axum = "0.4.0"
|
||||||
|
axum = { version = "0.7.5", features = ["ws", "macros"] }
|
||||||
|
directories = "5.0.1"
|
||||||
|
tokio = { version = "1.38.0", features = ["full"] }
|
||||||
|
tower-http = { version = "0.5.2", features = ["trace", "fs"] }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
|
82
flake.lock
Normal file
82
flake.lock
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"crane": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1718730147,
|
||||||
|
"narHash": "sha256-QmD6B6FYpuoCqu6ZuPJH896ItNquDkn0ulQlOn4ykN8=",
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"rev": "32c21c29b034d0a93fdb2379d6fabc40fc3d0e6c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681202837,
|
||||||
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1719010183,
|
||||||
|
"narHash": "sha256-8HMWaqpyjbVeEsmy/A2H6VFtW/Wr71vkPLnpTiAXu+8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "0f620ca71fa69abb411a6c78739a9b171a0a95a6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "release-24.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"crane": "crane",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
134
flake.nix
Normal file
134
flake.nix
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/release-24.05";
|
||||||
|
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
|
crane = {
|
||||||
|
url = "github:ipetkov/crane";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
crane,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
|
||||||
|
craneLib = crane.mkLib pkgs;
|
||||||
|
|
||||||
|
commonArgs = {
|
||||||
|
src = craneLib.cleanCargoSource ./.;
|
||||||
|
strictDeps = true;
|
||||||
|
nativeBuildInputs = with pkgs; [ pkg-config ];
|
||||||
|
buildInputs = with pkgs; [ openssl ];
|
||||||
|
};
|
||||||
|
|
||||||
|
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||||
|
|
||||||
|
my-crate = craneLib.buildPackage (
|
||||||
|
commonArgs
|
||||||
|
// {
|
||||||
|
src = ./.; # Allow access to assets, like ./templates
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
meta.mainProgram = "made-you-look";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
checks = {
|
||||||
|
inherit my-crate;
|
||||||
|
my-crate-fmt = craneLib.cargoFmt { inherit (commonArgs) src; };
|
||||||
|
};
|
||||||
|
|
||||||
|
packages.default = my-crate;
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
inherit (commonArgs) buildInputs;
|
||||||
|
|
||||||
|
nativeBuildInputs =
|
||||||
|
with pkgs;
|
||||||
|
(
|
||||||
|
[
|
||||||
|
rustc
|
||||||
|
cargo
|
||||||
|
rustfmt
|
||||||
|
rust-analyzer
|
||||||
|
clippy
|
||||||
|
cargo-feature
|
||||||
|
cargo-watch
|
||||||
|
curl
|
||||||
|
]
|
||||||
|
++ commonArgs.nativeBuildInputs
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// {
|
||||||
|
|
||||||
|
nixosModules.default =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.services.made-you-look;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.made-you-look = {
|
||||||
|
enable = lib.mkEnableOption "Enable Warthunder Leak Counter";
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 26023;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
systemd.services.made-you-look = {
|
||||||
|
script = lib.getExe self.packages.${pkgs.system}.default;
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
MADE_YOU_LOOK_SERVE_PORT = toString cfg.port;
|
||||||
|
};
|
||||||
|
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nixosConfigurations.test-server = nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [
|
||||||
|
self.nixosModules.default
|
||||||
|
(nixpkgs + "/nixos/modules/virtualisation/qemu-vm.nix")
|
||||||
|
(
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
services.made-you-look.enable = true;
|
||||||
|
users.users.root.password = "root";
|
||||||
|
networking.firewall.enable = false;
|
||||||
|
virtualisation.forwardPorts = [
|
||||||
|
{
|
||||||
|
from = "host";
|
||||||
|
host.port = 8888;
|
||||||
|
guest.port = config.services.made-you-look.port;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
system.stateVersion = "24.05";
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
154
src/lib.rs
Normal file
154
src/lib.rs
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
use std::{collections::HashSet, future::Future, net::SocketAddr, pin::Pin, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use askama::Template;
|
||||||
|
use axum::{
|
||||||
|
extract::{ConnectInfo, State},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use tokio::{fs::File, io::AsyncWriteExt, net::TcpListener, sync::RwLock};
|
||||||
|
|
||||||
|
async fn routes() -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(home))
|
||||||
|
.with_state(AppState::load().await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
view_count: Arc<RwLock<u32>>,
|
||||||
|
seen_ips: Arc<RwLock<HashSet<SocketAddr>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
async fn load() -> Self {
|
||||||
|
let view_count = match read_count().await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!(message = "Failed to read count", error = e.to_string(),);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let view_count = Arc::new(RwLock::new(view_count));
|
||||||
|
let seen_ips = Arc::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
view_count,
|
||||||
|
seen_ips,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn increment(&self, ip: SocketAddr) -> u32 {
|
||||||
|
if self.seen_ips.read().await.contains(&ip) {
|
||||||
|
let view_count = self.view_count.read().await;
|
||||||
|
return *view_count;
|
||||||
|
}
|
||||||
|
self.seen_ips.write().await.insert(ip);
|
||||||
|
|
||||||
|
let mut view_count = self.view_count.write().await;
|
||||||
|
*view_count += 1;
|
||||||
|
|
||||||
|
if let Err(e) = write_count(*view_count).await {
|
||||||
|
tracing::error!(message = "Failed to write count", error = e.to_string(),);
|
||||||
|
}
|
||||||
|
|
||||||
|
*view_count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
pub struct HomeTemplate {
|
||||||
|
view_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_path() -> anyhow::Result<std::path::PathBuf> {
|
||||||
|
let project_dirs = directories::ProjectDirs::from("com", "lelgenio", "made-you-look")
|
||||||
|
.context("building project dirs path")?;
|
||||||
|
|
||||||
|
let state_dir = project_dirs.state_dir().context("getting state dir")?;
|
||||||
|
|
||||||
|
Ok(state_dir.join("state"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_count() -> anyhow::Result<u32> {
|
||||||
|
let file_path = state_path()?;
|
||||||
|
let s = tokio::fs::read_to_string(file_path)
|
||||||
|
.await
|
||||||
|
.context("reading count")?;
|
||||||
|
|
||||||
|
s.parse().context("parsing count")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_count(new_count: u32) -> anyhow::Result<()> {
|
||||||
|
let file_path = state_path()?;
|
||||||
|
|
||||||
|
// let s = std::fs::read_to_string(file_path).context("reading count")?;
|
||||||
|
tokio::fs::create_dir_all(file_path.parent().context("Getting state file dirname")?).await?;
|
||||||
|
let mut file = File::create(file_path).await.context("Creating")?;
|
||||||
|
|
||||||
|
file.write(new_count.to_string().as_bytes())
|
||||||
|
.await
|
||||||
|
.context("Writting new count")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn home(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
ConnectInfo(ip): ConnectInfo<SocketAddr>,
|
||||||
|
) -> HomeTemplate {
|
||||||
|
tracing::info!(ip = ip.to_string());
|
||||||
|
|
||||||
|
let view_count = state.increment(ip).await;
|
||||||
|
|
||||||
|
HomeTemplate { view_count }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RunningServer {
|
||||||
|
pub port: u16,
|
||||||
|
pub server: Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(config: Config) -> Result<RunningServer> {
|
||||||
|
setup_tracing();
|
||||||
|
|
||||||
|
let router = routes()
|
||||||
|
.await
|
||||||
|
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||||
|
.into_make_service_with_connect_info::<SocketAddr>();
|
||||||
|
|
||||||
|
let tcp_listener = TcpListener::bind(format!("0.0.0.0:{}", config.port)).await?;
|
||||||
|
|
||||||
|
let port = tcp_listener.local_addr()?.port();
|
||||||
|
|
||||||
|
tracing::info!("Listening on http://localhost:{port}");
|
||||||
|
let server = Box::pin(async move {
|
||||||
|
axum::serve(tcp_listener, router).await?;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(RunningServer { port, server })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_tracing() {
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
let log_filter =
|
||||||
|
std::env::var("MADE_YOU_LOOK_LOG").unwrap_or_else(|_| "made_you_look=debug,warn".into());
|
||||||
|
|
||||||
|
eprintln!("RUST_LOG: {log_filter}");
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::EnvFilter::new(log_filter))
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.try_init()
|
||||||
|
.ok();
|
||||||
|
}
|
16
src/main.rs
Normal file
16
src/main.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use made_you_look::{run, Config};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let config = Config {
|
||||||
|
port: std::env::var("MADE_YOU_LOOK_SERVE_PORT")
|
||||||
|
.ok()
|
||||||
|
.and_then(|p| p.parse().ok())
|
||||||
|
.unwrap_or(8000u16),
|
||||||
|
};
|
||||||
|
|
||||||
|
run(config).await?.server.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
38
templates/base.html
Normal file
38
templates/base.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="/styles/main.css" />
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: "sans";
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-title {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
font-size: 4rem;
|
||||||
|
color: #ef2843;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-label {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %}
|
||||||
|
<p>Placeholder content</p>
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
10
templates/index.html
Normal file
10
templates/index.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "base.html" %} {% block title %} Made You Look! {% endblock %} {%
|
||||||
|
block content %}
|
||||||
|
<div class="main-title">
|
||||||
|
<h1>MADE YOU LOOK!</h1>
|
||||||
|
<h2 class="subtitle">
|
||||||
|
<span class="count"> {{ view_count }} </span>
|
||||||
|
<span class="count-label"> People fell for this </span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue