Initial commit

This commit is contained in:
Ivan R. 2024-10-26 19:11:54 +05:00
commit b9d41c7fc4
Signed by: lumin
GPG key ID: 9B2CA5D12844D4D0
28 changed files with 1996 additions and 0 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
/.git
/target
/.editorconfig
/.gitignore

11
.editorconfig Normal file
View file

@ -0,0 +1,11 @@
root = true
[*]
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 2
[*.rs]
indent_size = 4

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1608
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

12
Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "comfycamp"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.41.0", features = ["full"] }
axum = "0.7.7"
tera = "1.20.0"
lazy_static = "1.5.0"
tower-http = { version = "0.6.1", features = ["fs"] }
notify = "7.0.0"

15
Dockerfile Normal file
View file

@ -0,0 +1,15 @@
FROM rust:1.82-slim-bookworm AS builder
WORKDIR /usr/src/comfycamp
COPY . .
RUN cargo install --path .
FROM debian:bookworm-slim
WORKDIR /usr/src/comfycamp
RUN mkdir -p src/web
COPY src/web/assets src/web/assets
COPY src/web/templates src/web/templates
COPY --from=builder /usr/local/cargo/bin/comfycamp /usr/local/bin/comfycamp
CMD ["comfycamp"]

0
src/internal/mod.rs Normal file
View file

41
src/main.rs Normal file
View file

@ -0,0 +1,41 @@
use axum::{routing::get, Router};
use tower_http::services::ServeDir;
use tokio::signal;
use crate::web::controllers;
mod internal;
mod web;
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(controllers::home))
.nest_service("/assets", ServeDir::new("src/web/assets"));
let listener = tokio::net::TcpListener::bind("0.0.0.0:4000").await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to start CTRL+C handler");
};
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
tokio::select!(
_ = ctrl_c => {},
_ = terminate => {},
);
}

View file

@ -0,0 +1,36 @@
.service {
margin-top: 28px;
}
.service .title {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 8px;
}
.service .title h3 {
margin-right: 8px;
margin-top: 0;
margin-bottom: 0;
}
.service .tag {
font-size: 12px;
background-color: #333;
padding: 4px 6px;
border-radius: 6px;
}
.service .link {
color: var(--accent);
display: flex;
gap: 8px;
align-items: center;
}
.service svg {
width: 16px;
height: 16px;
}

View file

@ -0,0 +1,76 @@
:root {
--bg: #13151a;
--accent: #b283e5;
--input-bg: #28253c;
--input-border: #4c4c6d;
}
html {
font-family: Georgia, serif;
background-color: var(--bg);
color: white;
font-size: 16px;
}
* {
box-sizing: border-box;
}
*::selection {
background-color: var(--accent);
color: var(--bg);
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
margin-top: 36px;
margin-bottom: 24px;
}
h2 {
margin-top: 32px;
}
nav,
main,
footer {
max-width: 800px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
nav {
display: flex;
gap: 16px;
padding-top: 16px;
}
nav .space {
flex-grow: 1;
}
footer {
margin-top: 32px;
}
footer .link-row {
margin-top: 12px;
display: flex;
flex-wrap: wrap;
gap: 12px;
}
footer .link-row a {
display: flex;
align-items: center;
gap: 8px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -0,0 +1 @@
<svg width="512" height="512" viewBox="0 0 135.467 135.467" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path style="fill:#2b0759;fill-opacity:1;stroke:none;stroke-width:1.8432;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" d="M0 0h135.467v135.467H0z"/><path d="m34.467 56.644 33.266-25.873L101 56.644v40.66a7.393 7.393 0 0 1-7.393 7.392H41.86a7.393 7.393 0 0 1-7.393-7.393z" style="fill:none;stroke:#fff;stroke-width:12.793;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"/></svg>

After

Width:  |  Height:  |  Size: 541 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M320-240 80-480l240-240 57 57-184 184 183 183-56 56Zm320 0-57-57 184-184-183-183 56-56 240 240-240 240Z"/></svg>

After

Width:  |  Height:  |  Size: 229 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"/></svg>

After

Width:  |  Height:  |  Size: 301 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>

After

Width:  |  Height:  |  Size: 328 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF"><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF"><path d="M.632.55v22.9H2.28V24H0V0h2.28v.55zm7.043 7.26v1.157h.033c.309-.443.683-.784 1.117-1.024.433-.245.936-.365 1.5-.365.54 0 1.033.107 1.481.314.448.208.785.582 1.02 1.108.254-.374.6-.706 1.034-.992.434-.287.95-.43 1.546-.43.453 0 .872.056 1.26.167.388.11.716.286.993.53.276.245.489.559.646.951.152.392.23.863.23 1.417v5.728h-2.349V11.52c0-.286-.01-.559-.032-.812a1.755 1.755 0 0 0-.18-.66 1.106 1.106 0 0 0-.438-.448c-.194-.11-.457-.166-.785-.166-.332 0-.6.064-.803.189a1.38 1.38 0 0 0-.48.499 1.946 1.946 0 0 0-.231.696 5.56 5.56 0 0 0-.06.785v4.768h-2.35v-4.8c0-.254-.004-.503-.018-.752a2.074 2.074 0 0 0-.143-.688 1.052 1.052 0 0 0-.415-.503c-.194-.125-.476-.19-.854-.19-.111 0-.259.024-.439.074-.18.051-.36.143-.53.282-.171.138-.319.337-.439.595-.12.259-.18.6-.18 1.02v4.966H5.46V7.81zm15.693 15.64V.55H21.72V0H24v24h-2.28v-.55z"/></svg>

After

Width:  |  Height:  |  Size: 923 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF"><path d="m3.401 4.594 1.025.366 3.08.912c-.01.18-.016.361-.016.543 0 3.353 1.693 7.444 4.51 10.387 2.817-2.943 4.51-7.034 4.51-10.387 0-.182-.006-.363-.016-.543l3.08-.912 1.025-.366L24 3.276C23.854 8.978 19.146 14.9 13.502 18.17c1.302 1.028 2.778 1.81 4.388 2.215v.114l.004.001v.224a14.55 14.55 0 0 1-4.829-1.281A20.909 20.909 0 0 1 12 18.966c-.353.17-.708.329-1.065.477a14.55 14.55 0 0 1-4.829 1.281V20.5l.004-.001v-.113c1.61-.406 3.086-1.188 4.389-2.216C4.854 14.9.146 8.978 0 3.276l3.401 1.318Z"/></svg>

After

Width:  |  Height:  |  Size: 582 B

View file

@ -0,0 +1,6 @@
use axum::response::Html;
use crate::web::templates;
pub async fn home() -> Html<String> {
templates::render("home.html", &tera::Context::new())
}

View file

@ -0,0 +1,3 @@
pub mod home;
pub use home::home;

2
src/web/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod controllers;
pub mod templates;

29
src/web/templates.rs Normal file
View file

@ -0,0 +1,29 @@
use lazy_static::lazy_static;
use axum::response::Html;
use tera::Tera;
lazy_static! {
pub static ref TEMPLATES: Tera = {
let mut tera = match Tera::new("src/web/templates/**/*") {
Ok(t) => t,
Err(e) => {
println!("Templates parsing error(s): {}", e);
::std::process::exit(1);
}
};
tera.autoescape_on(vec![".html"]);
tera
};
}
/// Render HTML template.
///
/// Parameters:
/// - `name`: html file path relative to `templates` dir.
/// - `ctx`: context with parameters for rendering.
pub fn render(name: &str, ctx: &tera::Context) -> Html<String> {
match TEMPLATES.render(name, ctx) {
Ok(res) => Html(res),
Err(err) => Html(err.to_string()),
}
}

View file

@ -0,0 +1,85 @@
{% extends "layout.html" %}
{% block head %}
<link rel="stylesheet" href="/assets/css/home.css" />
{% endblock %}
{% block content %}
<h1>Уютный домик</h1>
<p>
У меня есть несколько проектов, запущенных на домашнем сервере.
Я буду рад, если они будут полезны другим людям.
</p>
<p>
Создайте один аккаунт, чтобы получить доступ к большинству сервисов.
</p>
<p>
Не стоит ожидать большой надёжности, однако я прикладываю все усилия,
чтобы сервисы были доступны 24/7.
</p>
<p><i>- Иван, администратор comfycamp.space</i></p>
<div class="service">
<div class="title">
<h3>Mastodon</h3>
</div>
<a class="link" href="https://m.comfycamp.space" target="_blank">
m.comfycamp.space <img src="/assets/icons/external.svg" width="20" height="20" alt="" />
</a>
<p>
Микроблоги с поддержкой fediverse.
</p>
</div>
<div class="service">
<div class="title">
<h3>Peertube</h3>
</div>
<a class="link" href="https://v.comfycamp.space" target="_blank">
v.comfycamp.space <img src="/assets/icons/external.svg" width="20" height="20" alt="" />
</a>
<p>
Видеохостинг, альтернатива YouTube.
</p>
</div>
<div class="service">
<div class="title">
<h3>Matrix</h3>
</div>
<a class="link">
matrix.comfycamp.space
</a>
<p>
Современный протокол для общения.
</p>
</div>
<div class="service">
<div class="title">
<h3>Forgejo</h3>
</div>
<a class="link" href="https://git.comfycamp.space" target="_blank">
git.comfycamp.space <img src="/assets/icons/external.svg" width="20" height="20" alt="" />
</a>
<p>
Хостинг для git-проектов.
</p>
</div>
<div class="service">
<div class="title">
<h3>Nextcloud</h3>
</div>
<a class="link" href="https://nc.comfycamp.space" target="_blank">
nc.comfycamp.space <img src="/assets/icons/external.svg" width="20" height="20" alt="" />
</a>
<p>
Облако, календарь, задачи.
</p>
</div>
{% endblock %}

View file

@ -0,0 +1,60 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>Comfy Camp</title>
<link rel="stylesheet" href="/assets/css/layout.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/assets/favicons/vector.svg" />
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicons/16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicons/32.png" />
<link rel="apple-touch-icon" type="image/png" href="/assets/favicons/180.png" />
<link rel="icon" type="image/png" sizes="192x192" href="/assets/favicons/192.png" />
<link rel="shortcut icon" type="image/x-icon" href="/assets/favicons/shortcut.ico" />
{% block head %}{% endblock %}
</head>
<body>
<nav>
<a href="/">Главная</a>
<div class="space"></div>
<a href="https://auth.comfycamp.space/if/flow/enrollment/">Зарегистрироваться</a>
</nav>
<main>
<div>
{% block content %}{% endblock %}
</div>
</main>
<footer>
<div class="link-row">
<a href="https://git.comfycamp.space/lumin/comfycamp">
<img src="/assets/icons/code.svg" height="20" width="20" alt="" />
Исходный код
</a>
</div>
<div class="link-row">
<a href="https://m.comfycamp.space/@lumin" rel="me">
<img src="/assets/icons/mastodon.svg" height="20" width="20" alt="" />
Mastodon
</a>
<a href="mailto:admin@comfycamp.space">
<img src="/assets/icons/mail.svg" height="20" width="20" alt="" />
admin@comfycamp.space
</a>
</div>
<div class="link-row">
<a href="https://matrix.to/#/@lumin:matrix.comfycamp.space">
<img src="/assets/icons/matrix.svg" height="20" width="20" alt="" />
@lumin:matrix.comfycamp.space
</a>
<a href="xmpp://lumin@xmpp.comfycamp.space">
<img src="/assets/icons/xmpp.svg" height="20" width="20" alt="" />
lumin@xmpp.comfycamp.space
</a>
</div>
</footer>
</body>
</html>