A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.

use env instead of cmd args

Changed files
+82 -11
src
+26 -10
README.md
··· 1 1 # SimpleLink 2 2 3 - A very performant and light (2MB in memory) link shortener and tracker. Written in Rust and React and uses Postgres. 3 + A very performant and light (2MB in memory) link shortener and tracker. Written in Rust and React and uses Postgres or SQLite. 4 4 5 5 ![MainView](readme_img/mainview.jpg) 6 6 ··· 10 10 11 11 ### From Docker: 12 12 13 - ```Bash 13 + ```bash 14 14 docker run -p 8080:8080 \ 15 15 -e JWT_SECRET=change-me-in-production \ 16 + -e SIMPLELINK_USER=admin@example.com \ 17 + -e SIMPLELINK_PASS=your-secure-password \ 16 18 -v simplelink_data:/data \ 17 19 ghcr.io/waveringana/simplelink:v2 18 20 ``` 19 21 20 - Find the admin-setup-token pasted into the terminal output, or in admin-setup-token.txt in the container's root. 22 + ### Environment Variables 21 23 22 - This is needed to register with the frontend. (TODO, register admin account with ENV) 24 + - `JWT_SECRET`: Required. Used for JWT token generation 25 + - `SIMPLELINK_USER`: Optional. If set along with SIMPLELINK_PASS, creates an admin user on first run 26 + - `SIMPLELINK_PASS`: Optional. Admin user password 27 + - `DATABASE_URL`: Optional. Postgres connection string. If not set, uses SQLite 28 + - `INITIAL_LINKS`: Optional. Semicolon-separated list of initial links in format "url,code;url2,code2" 29 + - `SERVER_HOST`: Optional. Default: "127.0.0.1" 30 + - `SERVER_PORT`: Optional. Default: "8080" 31 + 32 + If `SIMPLELINK_USER` and `SIMPLELINK_PASS` are not passed, an admin-setup-token is pasted to the console and as a text file in the project root. 23 33 24 34 ### From Docker Compose: 25 35 26 - Edit the docker-compose.yml file. It comes included with a postgressql db for use 36 + Edit the docker-compose.yml file. It comes included with a PostgreSQL db configuration. 27 37 28 38 ## Build 29 39 ··· 31 41 32 42 First configure .env.example and save it to .env 33 43 34 - If DATABASE_URL is set, it will connect to a Postgres DB. If blank, it will use an sqlite db in /data 35 - 36 44 ```bash 37 45 git clone https://github.com/waveringana/simplelink && cd simplelink 38 46 ./build.sh 39 47 cargo run 40 48 ``` 41 49 42 - On an empty database, an admin-setup-token.txt is created as well as pasted into the terminal output. This is needed to make the admin account. 43 - 44 - Alternatively if you want a binary form 50 + Alternatively for a binary build: 45 51 46 52 ```bash 47 53 ./build.sh --binary ··· 55 61 docker build -t simplelink . 56 62 docker run -p 8080:8080 \ 57 63 -e JWT_SECRET=change-me-in-production \ 64 + -e SIMPLELINK_USER=admin@example.com \ 65 + -e SIMPLELINK_PASS=your-secure-password \ 58 66 -v simplelink_data:/data \ 59 67 simplelink 60 68 ``` ··· 62 70 ### From Docker Compose 63 71 64 72 Adjust the included docker-compose.yml to your liking; it includes a postgres config as well. 73 + 74 + ## Features 75 + 76 + - Support for both PostgreSQL and SQLite databases 77 + - Initial links can be configured via environment variables 78 + - Admin user can be created on first run via environment variables 79 + - Link click tracking and statistics 80 + - Lightweight and performant
+56 -1
src/main.rs
··· 5 5 use simplelink::check_and_generate_admin_token; 6 6 use simplelink::{create_db_pool, run_migrations}; 7 7 use simplelink::{handlers, AppState}; 8 - use tracing::info; 8 + use sqlx::{Postgres, Sqlite}; 9 + use tracing::{error, info}; 9 10 11 + #[derive(Parser, Debug)] 12 + #[command(author, version, about, long_about = None)] 10 13 #[derive(RustEmbed)] 11 14 #[folder = "static/"] 12 15 struct Asset; ··· 34 37 // Create database connection pool 35 38 let pool = create_db_pool().await?; 36 39 run_migrations(&pool).await?; 40 + 41 + // First check if admin credentials are provided in environment variables 42 + let admin_credentials = match ( 43 + std::env::var("SIMPLELINK_USER"), 44 + std::env::var("SIMPLELINK_PASS"), 45 + ) { 46 + (Ok(user), Ok(pass)) => Some((user, pass)), 47 + _ => None, 48 + }; 49 + 50 + if let Some((email, password)) = admin_credentials { 51 + // Now check for existing users 52 + let user_count = match &pool { 53 + DatabasePool::Postgres(pool) => { 54 + let mut tx = pool.begin().await?; 55 + let count = 56 + sqlx::query_as::<Postgres, (i64,)>("SELECT COUNT(*)::bigint FROM users") 57 + .fetch_one(&mut *tx) 58 + .await? 59 + .0; 60 + tx.commit().await?; 61 + count 62 + } 63 + DatabasePool::Sqlite(pool) => { 64 + let mut tx = pool.begin().await?; 65 + let count = sqlx::query_as::<Sqlite, (i64,)>("SELECT COUNT(*) FROM users") 66 + .fetch_one(&mut *tx) 67 + .await? 68 + .0; 69 + tx.commit().await?; 70 + count 71 + } 72 + }; 73 + 74 + if user_count == 0 { 75 + info!("No users found, creating admin user: {}", email); 76 + match create_admin_user(&pool, &email, &password).await { 77 + Ok(_) => info!("Successfully created admin user"), 78 + Err(e) => { 79 + error!("Failed to create admin user: {}", e); 80 + return Err(anyhow::anyhow!("Failed to create admin user: {}", e)); 81 + } 82 + } 83 + } 84 + } else { 85 + info!( 86 + "No admin credentials provided in environment variables, skipping admin user creation" 87 + ); 88 + } 89 + 90 + // Create initial links from environment variables 91 + create_initial_links(&pool).await?; 37 92 38 93 let admin_token = check_and_generate_admin_token(&pool).await?; 39 94