nixpacks/providers/
staticfile.rs1use super::Provider;
2use crate::nixpacks::{
3 app::{App, StaticAssets},
4 environment::Environment,
5 nix::pkg::Pkg,
6 plan::{
7 phase::{Phase, StartPhase},
8 BuildPlan,
9 },
10};
11use anyhow::Result;
12use indoc::formatdoc;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::fmt::Write as _;
16
17#[derive(Serialize, Deserialize, Default, Debug)]
18pub struct Staticfile {
19 pub root: Option<String>,
20 pub directory: Option<String>,
21 pub gzip: Option<String>,
22 pub status_code: Option<HashMap<u32, String>>,
23}
24
25pub struct StaticfileProvider {}
26
27impl Provider for StaticfileProvider {
28 fn name(&self) -> &'static str {
29 "staticfile"
30 }
31
32 fn detect(&self, app: &App, _env: &Environment) -> Result<bool> {
33 Ok(app.includes_file("Staticfile")
34 || app.includes_directory("public")
35 || app.includes_directory("index")
36 || app.includes_directory("dist")
37 || app.includes_file("index.html"))
38 }
39
40 fn get_build_plan(&self, app: &App, env: &Environment) -> Result<Option<BuildPlan>> {
41 let mut setup = Phase::setup(Some(vec![Pkg::new("nginx")]));
42 setup.add_cmd("mkdir /etc/nginx/ /var/log/nginx/ /var/cache/nginx/");
43
44 let shell_cmd = "[[ -z \"${PORT}\" ]] && echo \"Environment variable PORT not found. Using PORT 80\" || sed -i \"s/0.0.0.0:80/$PORT/g\"";
46 let start = StartPhase::new(format!(
47 "{shell_cmd} {conf_location} && nginx -c {conf_location}",
48 shell_cmd = shell_cmd,
49 conf_location = app.asset_path("nginx.conf"),
50 ));
51
52 let static_assets = StaticfileProvider::get_static_assets(app, env)?;
53
54 let mut plan = BuildPlan::new(&vec![setup], Some(start));
55 plan.add_static_assets(static_assets);
56
57 Ok(Some(plan))
58 }
59}
60
61impl StaticfileProvider {
62 pub fn get_root(app: &App, env: &Environment, staticfile_root: String) -> String {
63 let mut root = String::new();
64 if let Some(staticfile_root) = env.get_config_variable("STATICFILE_ROOT") {
65 root = staticfile_root;
66 } else if !staticfile_root.is_empty() {
67 root = staticfile_root;
68 } else if app.includes_directory("public") {
69 root = "public".to_string();
70 } else if app.includes_directory("dist") {
71 root = "dist".to_string();
72 } else if app.includes_directory("index") {
73 root = "index".to_string();
74 }
75
76 root
77 }
78
79 fn get_static_assets(app: &App, env: &Environment) -> Result<StaticAssets> {
80 let mut assets = StaticAssets::new();
81
82 let mut mime_types = "include /nix/store/*-user-environment/conf/mime.types;".to_string();
83 if app.includes_file("mime.types") {
84 assets.insert("mime.types".to_string(), app.read_file("mime.types")?);
85 mime_types = "include\tmime.types;".to_string();
86 }
87
88 let mut auth_basic = String::new();
89 if app.includes_file("Staticfile.auth") {
90 assets.insert(".htpasswd".to_string(), app.read_file("Staticfile.auth")?);
91 auth_basic = format!(
92 "auth_basic\t\"Password Required\";\nauth_basic_user_file\t{};",
93 app.asset_path(".htpasswd")
94 );
95 }
96
97 let staticfile: Staticfile = app.read_yaml("Staticfile").unwrap_or_default();
98 let root = StaticfileProvider::get_root(app, env, staticfile.root.unwrap_or_default());
99 let gzip = staticfile.gzip.unwrap_or_else(|| "on".to_string());
100 let directory = staticfile.directory.unwrap_or_else(|| "off".to_string());
101 let status_code = staticfile.status_code.unwrap_or_default();
102 let mut error_page = String::new();
103 for (key, value) in status_code {
104 writeln!(error_page, "\terror_page {key} {value};")?;
105 }
106
107 let nginx_conf = formatdoc! {"
108 daemon off;
109 error_log /dev/stdout info;
110 worker_processes auto;
111 events {{
112 worker_connections 1024;
113 }}
114
115 http {{
116 {mime_types}
117 access_log /dev/stdout;
118 default_type application/octet-stream;
119 sendfile on;
120 keepalive_timeout 60;
121 types_hash_max_size 4096;
122 server {{
123 listen 0.0.0.0:80;
124 gzip {gzip};
125 root /app/{root};
126 location / {{
127 {auth_basic}
128 autoindex {directory};
129 }}
130 {error_page}
131 }}
132 }}
133 ",
134 mime_types = mime_types,
135 gzip = gzip,
136 root = root,
137 auth_basic = auth_basic,
138 directory = directory,
139 error_page = error_page
140 };
141 assets.insert("nginx.conf".to_string(), nginx_conf);
142
143 Ok(assets)
144 }
145}