feat(deploy): deploy on shuttle.rs (#36)

* feat(deploy): deploy on shuttle.rs

* chore(deploy): add automated shuttle deploy workflow

* style(readme): update the formatting in README.md

* chore(deploy): optimize shuttle workflow

* fix(deploy): start the project

* fix(deploy): remove start step

This reverts commit 4f25921aeb.

* chore(deploy): expose the version for the public instance

* docs(lib): update the comment for shuttle entry-point

* chore(deploy): run the shuttle deployment on new tag
This commit is contained in:
Orhun Parmaksız 2023-05-14 18:03:53 +02:00 committed by GitHub
parent 019d1556da
commit 29ddef8df0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1466 additions and 11 deletions

View File

@ -2,6 +2,7 @@
/.git/
/.github/
/upload/
/shuttle/
# Files
.gitignore

31
.github/workflows/shuttle.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Deploy on Shuttle
on:
push:
tags:
- "v*.*.*"
jobs:
deploy:
name: Deploy
runs-on: ubuntu-22.04
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Install cargo-binstall
uses: taiki-e/install-action@cargo-binstall
- name: Install cargo-shuttle
run: cargo binstall -y cargo-shuttle
- name: Prepare for deployment
shell: bash
run: sed -i 's|default = \[\]|default = \["shuttle"\]|g' Cargo.toml
- name: Login
run: cargo shuttle login --api-key ${{ secrets.SHUTTLE_TOKEN }}
- name: Deploy
run: cargo shuttle deploy --allow-dirty --no-test

1306
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,15 @@ keywords = ["paste", "pastebin", "upload"]
categories = ["web-programming::http-server"]
include = ["src/**/*", "Cargo.*", "LICENSE", "README.md", "CHANGELOG.md"]
[features]
default = []
shuttle = [
"dep:shuttle-actix-web",
"dep:shuttle-runtime",
"dep:shuttle-static-folder",
"dep:tokio",
]
[dependencies]
actix-web = { version = "4.3.1", features = ["rustls"] }
actix-multipart = "0.6.0"
@ -21,7 +30,10 @@ env_logger = "0.10.0"
log = "0.4.17"
serde = "1.0.163"
futures-util = "0.3.28"
petname = { version = "1.1.3", default-features = false, features = ["std_rng", "default_dictionary"] }
petname = { version = "1.1.3", default-features = false, features = [
"std_rng",
"default_dictionary",
] }
rand = "0.8.5"
dotenvy = "0.15.7"
url = "2.3.1"
@ -34,6 +46,10 @@ humantime-serde = "1.1.1"
glob = "0.3.1"
ring = "0.16.20"
hotwatch = "0.4.5"
shuttle-actix-web = { version = "0.16.0", optional = true }
shuttle-runtime = { version = "0.16.0", optional = true }
shuttle-static-folder = { version = "0.16.0", optional = true }
tokio = { version = "1.28.1", optional = true }
[dependencies.config]
version = "0.13.3"
@ -54,7 +70,6 @@ actix-rt = "2.8.0"
[profile.dev]
opt-level = 0
debug = true
panic = "abort"
[profile.test]
opt-level = 0

View File

@ -12,6 +12,8 @@ $ curl https://paste.site.com/safe-toad.txt
some text
```
The public instance is available at [https://rustypaste.shuttleapp.rs](https://rustypaste.shuttleapp.rs) 🚀
## Features
- File upload & URL shortening & upload from URL

View File

@ -3,6 +3,7 @@ refresh_rate = "1s"
[server]
address = "127.0.0.1:8000"
#url = "https://rustypaste.shuttleapp.rs"
#workers=4
max_content_length = "10MB"
upload_path = "./upload"

53
shuttle/config.toml Normal file
View File

@ -0,0 +1,53 @@
[config]
refresh_rate = "1s"
[server]
address = "127.0.0.1:8000"
url = "https://rustypaste.shuttleapp.rs"
#workers=4
max_content_length = "10MB"
upload_path = "./upload"
timeout = "30s"
expose_version = true
landing_page = """
Submit files via HTTP POST here:
curl -F 'file=@example.txt' https://rustypaste.shuttleapp.rs
This will return the URL of the uploaded file.
Pastes expire every hour. Uploaded files might not be persistent.
Check out the GitHub repository: https://github.com/orhun/rustypaste
Command line tool is available : https://github.com/orhun/rustypaste-cli
If you liked this, consider supporting me: https://donate.orhun.dev <3
🦀
"""
[paste]
# random_url = { enabled = true, type = "petname", words = 2, separator = "-" }
random_url = { enabled = true, type = "alphanumeric", length = 6 }
default_extension = "txt"
mime_override = [
{ mime = "image/jpeg", regex = "^.*\\.jpg$" },
{ mime = "image/png", regex = "^.*\\.png$" },
{ mime = "image/svg+xml", regex = "^.*\\.svg$" },
{ mime = "video/webm", regex = "^.*\\.webm$" },
{ mime = "video/x-matroska", regex = "^.*\\.mkv$" },
{ mime = "application/octet-stream", regex = "^.*\\.bin$" },
{ mime = "text/plain", regex = "^.*\\.(log|txt|diff|sh|kt|rs|toml)$" },
]
mime_blacklist = [
"application/x-dosexec",
"application/java-archive",
"application/java-vm",
]
duplicate_files = true
default_expiry = "1h"
delete_expired_files = { enabled = true, interval = "1h" }

View File

@ -1,9 +1,10 @@
use actix_web::middleware::Logger;
use actix_web::web::Data;
#[cfg(not(feature = "shuttle"))]
use actix_web::{App, HttpServer};
use awc::ClientBuilder;
use hotwatch::{Event, Hotwatch};
use rustypaste::config::Config;
use rustypaste::config::{Config, ServerConfig};
use rustypaste::paste::PasteType;
use rustypaste::server;
use rustypaste::util;
@ -11,17 +12,28 @@ use rustypaste::CONFIG_ENV;
use std::env;
use std::fs;
use std::io::Result as IoResult;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::{mpsc, RwLock};
use std::thread;
use std::time::Duration;
#[cfg(feature = "shuttle")]
use {
actix_web::web::{self, ServiceConfig},
shuttle_actix_web::ShuttleActixWeb,
};
#[actix_web::main]
async fn main() -> IoResult<()> {
/// Sets up the application.
///
/// * loads the configuration
/// * initializes the logger
/// * creates the necessary directories
/// * spawns the threads
fn setup(config_folder: &Path) -> IoResult<(Data<RwLock<Config>>, ServerConfig, Hotwatch)> {
// Load the .env file.
dotenvy::dotenv().ok();
// Initialize logger.
#[cfg(not(feature = "shuttle"))]
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
// Parse configuration.
@ -30,7 +42,7 @@ async fn main() -> IoResult<()> {
env::remove_var(CONFIG_ENV);
PathBuf::from(path)
}
None => PathBuf::from("config.toml"),
None => config_folder.join("config.toml"),
};
let config = Config::parse(&config_path).expect("failed to parse config");
log::trace!("{:#?}", config);
@ -83,6 +95,7 @@ async fn main() -> IoResult<()> {
.unwrap_or_else(|_| panic!("failed to watch {config_path:?}"));
// Create a thread for cleaning up expired files.
let upload_path = server_config.upload_path.clone();
thread::spawn(move || loop {
let mut enabled = false;
if let Some(ref cleanup_config) = paste_config
@ -92,7 +105,7 @@ async fn main() -> IoResult<()> {
{
if cleanup_config.enabled {
log::debug!("Running cleanup...");
for file in util::get_expired_files(&server_config.upload_path) {
for file in util::get_expired_files(&upload_path) {
match fs::remove_file(&file) {
Ok(()) => log::info!("Removed expired file: {:?}", file),
Err(e) => log::error!("Cannot remove expired file: {}", e),
@ -118,6 +131,15 @@ async fn main() -> IoResult<()> {
}
});
Ok((config, server_config, hotwatch))
}
#[cfg(not(feature = "shuttle"))]
#[actix_web::main]
async fn main() -> IoResult<()> {
// Set up the application.
let (config, server_config, _) = setup(&PathBuf::new())?;
// Create an HTTP server.
let mut http_server = HttpServer::new(move || {
let http_client = ClientBuilder::new()
@ -144,3 +166,33 @@ async fn main() -> IoResult<()> {
// Run the server.
http_server.run().await
}
#[cfg(feature = "shuttle")]
#[shuttle_runtime::main]
async fn actix_web(
#[shuttle_static_folder::StaticFolder(folder = "shuttle")] static_folder: PathBuf,
) -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
// Set up the application.
let (config, server_config, _) = setup(&static_folder)?;
// Create the service.
let service_config = move |cfg: &mut ServiceConfig| {
let http_client = ClientBuilder::new()
.timeout(
server_config
.timeout
.unwrap_or_else(|| Duration::from_secs(30)),
)
.disable_redirects()
.finish();
cfg.service(
web::scope("")
.app_data(Data::clone(&config))
.app_data(Data::new(http_client))
.wrap(Logger::default())
.configure(server::configure_routes),
);
};
Ok(service_config.into())
}