update
This commit is contained in:
41
README.md
41
README.md
@@ -1,2 +1,39 @@
|
||||
# nais
|
||||
Nginx Autoindex Skin
|
||||
# NAIS
|
||||
|
||||
NAIS is a styled nginx autoindex page with convenient playlist download and
|
||||
link-copying actions.
|
||||
|
||||
[Website](https://meghdadfadaee.github.io/nais/) ·
|
||||
[GitHub](https://github.com/meghdadfadaee/nais)
|
||||
|
||||
## Installation
|
||||
|
||||
The interactive installer requires nginx, curl, root access through either the
|
||||
current user or `sudo`, and an nginx service managed by `systemctl` or
|
||||
`service`:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://meghdadfadaee.github.io/nais/setup.sh | sh
|
||||
```
|
||||
|
||||
It asks for:
|
||||
|
||||
- `server_name`: one hostname or IP address.
|
||||
- `root_path`: the absolute path whose files nginx should list.
|
||||
- `assets_path`: where the NAIS CSS, JavaScript, and favicon are installed.
|
||||
The default is `/var/www/nais-assets`.
|
||||
- `certificates_path`: an optional directory containing `fullchain.pem` and
|
||||
`privkey.pem`.
|
||||
|
||||
Without a certificates path, the generated site serves HTTP on port 80. With a
|
||||
certificates path, it serves HTTPS on port 443 and redirects HTTP to HTTPS.
|
||||
|
||||
The installer detects common nginx virtual-host include directories. If none is
|
||||
configured, it adds a managed `nais-sites/*.conf` include to nginx's main
|
||||
configuration. It validates changes with `nginx -t` before reloading or starting
|
||||
nginx. Re-running the installer refreshes the assets and replaces the managed
|
||||
NAIS site configuration.
|
||||
|
||||
The installer does not install nginx or obtain TLS certificates.
|
||||
The nginx worker user must have permission to traverse and read `root_path`; the
|
||||
installer does not change permissions on existing content.
|
||||
|
||||
184
index.html
Normal file
184
index.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#0f111a">
|
||||
<meta name="description" content="NAIS gives nginx autoindex a clean, modern interface with one-command setup.">
|
||||
<meta property="og:title" content="NAIS - Nginx Autoindex Skin">
|
||||
<meta property="og:description" content="A modern, lightweight interface for nginx autoindex.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://meghdadfadaee.github.io/nais/favicon.png">
|
||||
<title>NAIS - Nginx Autoindex Skin</title>
|
||||
<link rel="icon" href="./favicon.ico">
|
||||
<link rel="stylesheet" href="./landing.css">
|
||||
<script src="./landing.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ambient ambient-one" aria-hidden="true"></div>
|
||||
<div class="ambient ambient-two" aria-hidden="true"></div>
|
||||
|
||||
<header class="site-header">
|
||||
<a class="brand" href="./" aria-label="NAIS home">
|
||||
<img src="./favicon.png" alt="" width="42" height="42">
|
||||
<span>NAIS</span>
|
||||
</a>
|
||||
<nav class="site-nav" aria-label="Main navigation">
|
||||
<a href="#features">Features</a>
|
||||
<a href="#install">Install</a>
|
||||
<a class="github-link" href="https://github.com/meghdadfadaee/nais">GitHub</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div class="hero-copy">
|
||||
<div class="eyebrow"><span></span> Nginx autoindex, refined</div>
|
||||
<h1>Your files.<br><strong>Better presented.</strong></h1>
|
||||
<p class="hero-text">
|
||||
NAIS turns the default nginx directory listing into a fast,
|
||||
focused, and modern file browser without changing how nginx works.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a class="button button-primary" href="#install">Install NAIS</a>
|
||||
<a class="button button-secondary" href="https://github.com/meghdadfadaee/nais">
|
||||
View source
|
||||
</a>
|
||||
</div>
|
||||
<div class="quick-command">
|
||||
<code>curl -fsSL https://meghdadfadaee.github.io/nais/setup.sh | sh</code>
|
||||
<button class="copy-button" type="button" data-copy-command aria-label="Copy install command">
|
||||
<span class="copy-label">Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-wrap" id="preview" aria-label="Preview of the NAIS file browser">
|
||||
<div class="preview-glow" aria-hidden="true"></div>
|
||||
<div class="browser">
|
||||
<div class="browser-bar">
|
||||
<div class="traffic-lights" aria-hidden="true">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<div class="address"><span class="lock">⌁</span> files.example.com/releases/</div>
|
||||
<span class="browser-menu" aria-hidden="true">•••</span>
|
||||
</div>
|
||||
<div class="file-header">
|
||||
<span>Index of /releases/</span>
|
||||
<div>
|
||||
<button type="button">Copy Links</button>
|
||||
<button type="button">Download Playlist</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<a class="parent" href="#preview"><span class="file-icon"></span>../</a>
|
||||
<a class="folder" href="#preview"><span class="file-icon"></span>archive/</a>
|
||||
<a class="folder" href="#preview"><span class="file-icon"></span>latest/</a>
|
||||
<a href="#preview"><span class="file-icon"></span>nais-v1.2.0.zip</a>
|
||||
<a href="#preview"><span class="file-icon"></span>checksums.txt</a>
|
||||
<a href="#preview"><span class="file-icon"></span>release-notes.pdf</a>
|
||||
<p>— nginx autoindex —</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-pill"><span></span> Lightweight. No framework.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="metrics" aria-label="Project highlights">
|
||||
<div><strong>3</strong><span>small assets</span></div>
|
||||
<div><strong>1</strong><span>install command</span></div>
|
||||
<div><strong>0</strong><span>runtime dependencies</span></div>
|
||||
<div><strong>100%</strong><span>nginx native</span></div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="features">
|
||||
<div class="section-heading">
|
||||
<span class="section-kicker">Why NAIS</span>
|
||||
<h2>A directory listing people can actually use.</h2>
|
||||
<p>Small additions that make browsing and sharing files noticeably better.</p>
|
||||
</div>
|
||||
<div class="feature-grid">
|
||||
<article class="feature-card feature-card-wide">
|
||||
<span class="feature-number">01</span>
|
||||
<div class="feature-visual rows-visual" aria-hidden="true">
|
||||
<span class="pink"></span><span class="green"></span><span class="green short"></span><span class="blue"></span>
|
||||
</div>
|
||||
<h3>Clear at a glance</h3>
|
||||
<p>Folders, parent navigation, and files use distinct colors so every directory is easy to scan.</p>
|
||||
</article>
|
||||
<article class="feature-card">
|
||||
<span class="feature-number">02</span>
|
||||
<div class="feature-icon link-icon" aria-hidden="true"></div>
|
||||
<h3>Copy every link</h3>
|
||||
<p>Collect all file URLs from the current directory with one click.</p>
|
||||
</article>
|
||||
<article class="feature-card">
|
||||
<span class="feature-number">03</span>
|
||||
<div class="feature-icon play-icon" aria-hidden="true"></div>
|
||||
<h3>Build playlists</h3>
|
||||
<p>Export media links as a ready-to-open playlist directly from the listing.</p>
|
||||
</article>
|
||||
<article class="feature-card feature-card-wide install-card">
|
||||
<span class="feature-number">04</span>
|
||||
<div class="terminal-mini" aria-hidden="true">
|
||||
<div><i></i><i></i><i></i></div>
|
||||
<code><b>$</b> curl ... | sh</code>
|
||||
<code><b>✓</b> NAIS configured successfully.</code>
|
||||
</div>
|
||||
<h3>Setup that respects nginx</h3>
|
||||
<p>The installer detects common layouts, validates with <code>nginx -t</code>, and restores prior configuration if anything fails.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section install-section" id="install">
|
||||
<div class="install-copy">
|
||||
<span class="section-kicker">Get started</span>
|
||||
<h2>From default to polished in one command.</h2>
|
||||
<p>
|
||||
Run the installer, answer four short prompts, and NAIS handles
|
||||
the assets, nginx configuration, validation, and reload.
|
||||
</p>
|
||||
<ul class="check-list">
|
||||
<li>HTTP or optional HTTPS configuration</li>
|
||||
<li>Automatic nginx include detection</li>
|
||||
<li>Safe validation and rollback</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="terminal">
|
||||
<div class="terminal-bar">
|
||||
<div aria-hidden="true"><span></span><span></span><span></span></div>
|
||||
<p>install nais</p>
|
||||
<button type="button" data-copy-command><span class="copy-label">Copy</span></button>
|
||||
</div>
|
||||
<pre><span class="prompt">$</span> curl -fsSL https://meghdadfadaee.github.io/nais/setup.sh | sh
|
||||
|
||||
<span class="muted">Server name:</span> files.example.com
|
||||
<span class="muted">Root path:</span> /srv/files
|
||||
<span class="muted">Assets path:</span> /var/www/nais-assets
|
||||
<span class="muted">Certificates path (optional):</span>
|
||||
|
||||
<span class="success">✓</span> Downloading NAIS assets...
|
||||
<span class="success">✓</span> Validating nginx configuration...
|
||||
<span class="success">✓</span> NAIS configured successfully.</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cta">
|
||||
<img src="./favicon.png" alt="" width="56" height="56">
|
||||
<h2>Make the simple thing look good.</h2>
|
||||
<p>A focused autoindex theme, built from plain CSS and JavaScript.</p>
|
||||
<a class="button button-primary" href="https://github.com/meghdadfadaee/nais">Explore on GitHub</a>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<a class="brand footer-brand" href="./">
|
||||
<img src="./favicon.png" alt="" width="34" height="34">
|
||||
<span>NAIS</span>
|
||||
</a>
|
||||
<p>Nginx Autoindex Skin</p>
|
||||
<a href="https://github.com/meghdadfadaee/nais">GitHub</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
978
landing.css
Normal file
978
landing.css
Normal file
@@ -0,0 +1,978 @@
|
||||
:root {
|
||||
--bg: #0f111a;
|
||||
--bg-deep: #0a0c13;
|
||||
--surface: #151926;
|
||||
--surface-bright: #1a1f30;
|
||||
--row-hover: #1f2440;
|
||||
--text: #e6e6eb;
|
||||
--muted: #8b93a7;
|
||||
--accent: #7aa2f7;
|
||||
--folder: #9ece6a;
|
||||
--parent: #f7768e;
|
||||
--brand: #f7d759;
|
||||
--border: #2a2f4a;
|
||||
--page-width: 1180px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
button,
|
||||
a {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.ambient {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
border-radius: 50%;
|
||||
filter: blur(130px);
|
||||
opacity: .14;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ambient-one {
|
||||
top: -220px;
|
||||
right: -120px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.ambient-two {
|
||||
top: 550px;
|
||||
left: -350px;
|
||||
background: var(--folder);
|
||||
}
|
||||
|
||||
.site-header,
|
||||
main,
|
||||
footer {
|
||||
width: min(calc(100% - 40px), var(--page-width));
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 84px;
|
||||
border-bottom: 1px solid rgba(42, 47, 74, .7);
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
color: var(--text);
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
letter-spacing: .12em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.brand img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.site-nav a {
|
||||
text-decoration: none;
|
||||
transition: color .2s ease;
|
||||
}
|
||||
|
||||
.site-nav a:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.site-nav .github-link {
|
||||
padding: 8px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, .88fr) minmax(520px, 1.12fr);
|
||||
align-items: center;
|
||||
gap: 70px;
|
||||
min-height: 720px;
|
||||
padding: 74px 0 92px;
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.section-kicker {
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: .16em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.eyebrow span {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--folder);
|
||||
box-shadow: 0 0 0 5px rgba(158, 206, 106, .1), 0 0 18px rgba(158, 206, 106, .55);
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
margin: 22px 0 22px;
|
||||
font-size: clamp(54px, 6vw, 84px);
|
||||
line-height: .98;
|
||||
letter-spacing: -.065em;
|
||||
}
|
||||
|
||||
.hero h1 strong {
|
||||
color: var(--accent);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
max-width: 560px;
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 34px;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 46px;
|
||||
padding: 0 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 9px;
|
||||
font-size: 14px;
|
||||
font-weight: 750;
|
||||
text-decoration: none;
|
||||
transition: transform .2s ease, background .2s ease, border-color .2s ease;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.button-primary {
|
||||
background: var(--accent);
|
||||
color: #0c1020;
|
||||
}
|
||||
|
||||
.button-primary:hover {
|
||||
background: #96b8ff;
|
||||
}
|
||||
|
||||
.button-secondary {
|
||||
border-color: var(--border);
|
||||
background: rgba(21, 25, 38, .7);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.button-secondary:hover {
|
||||
border-color: var(--accent);
|
||||
background: var(--surface-bright);
|
||||
}
|
||||
|
||||
.quick-command {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
max-width: 560px;
|
||||
margin-top: 22px;
|
||||
padding: 10px 10px 10px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: rgba(10, 12, 19, .65);
|
||||
}
|
||||
|
||||
.quick-command code {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.copy-button,
|
||||
.terminal-bar button {
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
padding: 6px 9px;
|
||||
}
|
||||
|
||||
.copy-button:hover,
|
||||
.terminal-bar button:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.preview-wrap {
|
||||
position: relative;
|
||||
perspective: 1200px;
|
||||
}
|
||||
|
||||
.preview-glow {
|
||||
position: absolute;
|
||||
inset: 12% 5% -2%;
|
||||
z-index: -1;
|
||||
border-radius: 50%;
|
||||
background: rgba(122, 162, 247, .28);
|
||||
filter: blur(65px);
|
||||
}
|
||||
|
||||
.browser {
|
||||
overflow: hidden;
|
||||
border: 1px solid #343b5b;
|
||||
border-radius: 14px;
|
||||
background: var(--bg-deep);
|
||||
box-shadow: 0 35px 80px rgba(0, 0, 0, .38);
|
||||
transform: rotateY(-4deg) rotateX(1deg);
|
||||
animation: enter .8s ease-out both;
|
||||
}
|
||||
|
||||
.browser-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr 80px;
|
||||
align-items: center;
|
||||
min-height: 48px;
|
||||
padding: 0 14px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: #121520;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.traffic-lights span,
|
||||
.terminal-bar > div span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--parent);
|
||||
}
|
||||
|
||||
.traffic-lights span:nth-child(2),
|
||||
.terminal-bar > div span:nth-child(2) {
|
||||
background: var(--brand);
|
||||
}
|
||||
|
||||
.traffic-lights span:nth-child(3),
|
||||
.terminal-bar > div span:nth-child(3) {
|
||||
background: var(--folder);
|
||||
}
|
||||
|
||||
.address {
|
||||
overflow: hidden;
|
||||
padding: 5px 12px;
|
||||
border: 1px solid #252a3f;
|
||||
border-radius: 6px;
|
||||
background: #0c0f17;
|
||||
color: #737b91;
|
||||
font: 11px ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.lock {
|
||||
color: var(--folder);
|
||||
}
|
||||
|
||||
.browser-menu {
|
||||
justify-self: end;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.file-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 68px;
|
||||
padding: 0 22px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
font-size: 14px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.file-header div {
|
||||
display: flex;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.file-header button {
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
padding: 10px 0 20px;
|
||||
}
|
||||
|
||||
.file-list a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
padding: 9px 22px;
|
||||
color: var(--accent);
|
||||
font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
text-decoration: none;
|
||||
transition: background .15s ease, color .15s ease;
|
||||
}
|
||||
|
||||
.file-list a:hover {
|
||||
background: var(--row-hover);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.file-list .folder {
|
||||
color: var(--folder);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.file-list .parent {
|
||||
color: var(--parent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 2px;
|
||||
background: currentColor;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
.file-list p {
|
||||
margin: 17px 22px 0;
|
||||
color: var(--muted);
|
||||
font: 9px ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
position: absolute;
|
||||
right: -18px;
|
||||
bottom: -25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 13px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: rgba(21, 25, 38, .92);
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, .3);
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.status-pill span {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--folder);
|
||||
box-shadow: 0 0 12px var(--folder);
|
||||
}
|
||||
|
||||
.metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border-block: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.metrics div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 28px;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.metrics div:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.metrics strong {
|
||||
color: var(--text);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.metrics span {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 130px 0 0;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
max-width: 650px;
|
||||
margin-bottom: 44px;
|
||||
}
|
||||
|
||||
.section h2,
|
||||
.cta h2 {
|
||||
margin: 12px 0 15px;
|
||||
font-size: clamp(36px, 5vw, 58px);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -.05em;
|
||||
}
|
||||
|
||||
.section-heading p,
|
||||
.install-copy > p,
|
||||
.cta p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
position: relative;
|
||||
min-height: 300px;
|
||||
overflow: hidden;
|
||||
padding: 32px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(145deg, rgba(26, 31, 48, .9), rgba(17, 20, 31, .9));
|
||||
transition: border-color .25s ease, transform .25s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
border-color: #424b72;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.feature-card-wide {
|
||||
grid-row: span 2;
|
||||
min-height: 616px;
|
||||
}
|
||||
|
||||
.feature-number {
|
||||
color: #626b83;
|
||||
font: 11px ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
position: absolute;
|
||||
bottom: 72px;
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
letter-spacing: -.025em;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
position: absolute;
|
||||
right: 32px;
|
||||
bottom: 27px;
|
||||
left: 32px;
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.feature-visual {
|
||||
position: absolute;
|
||||
inset: 85px 45px auto;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
padding: 30px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: #0d1018;
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
|
||||
.rows-visual span {
|
||||
width: 78%;
|
||||
height: 10px;
|
||||
border-radius: 3px;
|
||||
background: var(--accent);
|
||||
box-shadow: -18px 0 0 -3px currentColor;
|
||||
}
|
||||
|
||||
.rows-visual .pink {
|
||||
width: 42%;
|
||||
background: var(--parent);
|
||||
color: var(--parent);
|
||||
}
|
||||
|
||||
.rows-visual .green {
|
||||
width: 65%;
|
||||
background: var(--folder);
|
||||
color: var(--folder);
|
||||
}
|
||||
|
||||
.rows-visual .short {
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.rows-visual .blue {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
position: absolute;
|
||||
top: 76px;
|
||||
left: 32px;
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
background: var(--bg-deep);
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.link-icon::before,
|
||||
.link-icon::after {
|
||||
position: absolute;
|
||||
width: 25px;
|
||||
height: 10px;
|
||||
border: 3px solid var(--accent);
|
||||
border-radius: 10px;
|
||||
content: "";
|
||||
transform: rotate(-35deg);
|
||||
}
|
||||
|
||||
.link-icon::before {
|
||||
top: 18px;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.link-icon::after {
|
||||
right: 11px;
|
||||
bottom: 17px;
|
||||
}
|
||||
|
||||
.play-icon::before {
|
||||
position: absolute;
|
||||
top: 19px;
|
||||
left: 23px;
|
||||
border-top: 12px solid transparent;
|
||||
border-bottom: 12px solid transparent;
|
||||
border-left: 18px solid var(--folder);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.install-card {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: auto;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
.terminal-mini {
|
||||
position: absolute;
|
||||
top: 78px;
|
||||
right: 32px;
|
||||
left: 32px;
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: var(--bg-deep);
|
||||
font: 11px ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
|
||||
.terminal-mini div {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.terminal-mini i {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--parent);
|
||||
}
|
||||
|
||||
.terminal-mini i:nth-child(2) {
|
||||
background: var(--brand);
|
||||
}
|
||||
|
||||
.terminal-mini i:nth-child(3),
|
||||
.terminal-mini code:last-child b {
|
||||
background: var(--folder);
|
||||
color: var(--folder);
|
||||
}
|
||||
|
||||
.terminal-mini b {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.install-section {
|
||||
display: grid;
|
||||
grid-template-columns: .8fr 1.2fr;
|
||||
align-items: center;
|
||||
gap: 90px;
|
||||
}
|
||||
|
||||
.check-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 28px 0 0;
|
||||
padding: 0;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.check-list li::before {
|
||||
margin-right: 10px;
|
||||
color: var(--folder);
|
||||
content: "✓";
|
||||
}
|
||||
|
||||
.terminal {
|
||||
overflow: hidden;
|
||||
border: 1px solid #343b5b;
|
||||
border-radius: 14px;
|
||||
background: var(--bg-deep);
|
||||
box-shadow: 0 30px 70px rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
.terminal-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
padding: 11px 14px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: #121520;
|
||||
}
|
||||
|
||||
.terminal-bar > div {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.terminal-bar p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.terminal-bar button {
|
||||
justify-self: end;
|
||||
padding: 5px 9px;
|
||||
}
|
||||
|
||||
.terminal pre {
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
padding: 30px;
|
||||
color: var(--text);
|
||||
font: 12px/2 ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
|
||||
.terminal .prompt {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.terminal .muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.terminal .success {
|
||||
color: var(--folder);
|
||||
}
|
||||
|
||||
.cta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 130px;
|
||||
padding: 90px 20px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
background:
|
||||
radial-gradient(circle at 50% 0%, rgba(122, 162, 247, .18), transparent 48%),
|
||||
var(--surface);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta h2 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.cta .button {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
margin-top: 48px;
|
||||
padding: 28px 0 36px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.footer-brand img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
footer > a:last-child {
|
||||
justify-self: end;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer > a:last-child:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@keyframes enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(18px) rotateY(-4deg) rotateX(1deg);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) rotateY(-4deg) rotateX(1deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 70px;
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
max-width: 650px;
|
||||
}
|
||||
|
||||
.preview-wrap {
|
||||
max-width: 700px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.install-section {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.site-header,
|
||||
main,
|
||||
footer {
|
||||
width: min(calc(100% - 28px), var(--page-width));
|
||||
}
|
||||
|
||||
.site-header {
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.site-nav a:not(.github-link) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero {
|
||||
min-height: auto;
|
||||
padding: 65px 0 80px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: clamp(48px, 15vw, 68px);
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.quick-command {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.browser {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.browser-bar {
|
||||
grid-template-columns: 58px 1fr 24px;
|
||||
}
|
||||
|
||||
.file-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
padding-block: 15px;
|
||||
}
|
||||
|
||||
.file-list a {
|
||||
padding-inline: 16px;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.metrics div {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.metrics div:nth-child(2) {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.metrics div:nth-child(n+3) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-top: 95px;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.feature-card,
|
||||
.feature-card-wide,
|
||||
.install-card {
|
||||
grid-column: auto;
|
||||
grid-row: auto;
|
||||
min-height: 330px;
|
||||
}
|
||||
|
||||
.feature-card-wide {
|
||||
min-height: 450px;
|
||||
}
|
||||
|
||||
.install-card {
|
||||
min-height: 390px;
|
||||
}
|
||||
|
||||
.install-card h3 {
|
||||
bottom: 115px;
|
||||
}
|
||||
|
||||
.terminal pre {
|
||||
padding: 22px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.cta {
|
||||
margin-top: 95px;
|
||||
padding-block: 65px;
|
||||
}
|
||||
|
||||
footer {
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
footer p {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: .01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
16
landing.js
Normal file
16
landing.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const installCommand = 'curl -fsSL https://meghdadfadaee.github.io/nais/setup.sh | sh';
|
||||
|
||||
document.querySelectorAll('[data-copy-command]').forEach((button) => {
|
||||
button.addEventListener('click', async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(installCommand);
|
||||
const label = button.querySelector('.copy-label');
|
||||
label.textContent = 'Copied';
|
||||
window.setTimeout(() => {
|
||||
label.textContent = 'Copy';
|
||||
}, 1800);
|
||||
} catch {
|
||||
window.prompt('Copy the install command:', installCommand);
|
||||
}
|
||||
});
|
||||
});
|
||||
332
setup.sh
Executable file
332
setup.sh
Executable file
@@ -0,0 +1,332 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
BASE_URL=${NAIS_BASE_URL:-https://meghdadfadaee.github.io/nais}
|
||||
DEFAULT_ASSETS_PATH=/var/www/nais-assets
|
||||
MANAGED_INCLUDE='include nais-sites/*.conf;'
|
||||
|
||||
say() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'nais setup: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
need_command() {
|
||||
command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
|
||||
}
|
||||
|
||||
prompt() {
|
||||
prompt_text=$1
|
||||
default_value=${2-}
|
||||
|
||||
if [ -n "$default_value" ]; then
|
||||
printf '%s [%s]: ' "$prompt_text" "$default_value" >/dev/tty
|
||||
else
|
||||
printf '%s: ' "$prompt_text" >/dev/tty
|
||||
fi
|
||||
|
||||
IFS= read -r answer </dev/tty || fail "could not read input from /dev/tty"
|
||||
if [ -z "$answer" ]; then
|
||||
answer=$default_value
|
||||
fi
|
||||
printf '%s' "$answer"
|
||||
}
|
||||
|
||||
validate_absolute_path() {
|
||||
value=$1
|
||||
label=$2
|
||||
case "$value" in
|
||||
/*) ;;
|
||||
*) fail "$label must be an absolute path" ;;
|
||||
esac
|
||||
case "$value" in
|
||||
*'
|
||||
'*) fail "$label must not contain newlines" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
nginx_quote() {
|
||||
value=$1
|
||||
case "$value" in
|
||||
*'"'*|*'\'*|*'$'*) fail "paths containing quotes, backslashes, or dollar signs are not supported" ;;
|
||||
esac
|
||||
printf '"%s"' "$value"
|
||||
}
|
||||
|
||||
run_root() {
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
"$@"
|
||||
else
|
||||
sudo "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
status=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
if [ "$status" -ne 0 ] && [ "${TRANSACTION_ACTIVE:-0}" -eq 1 ]; then
|
||||
restore_previous
|
||||
fi
|
||||
|
||||
if [ -n "${WORK_DIR:-}" ]; then
|
||||
rm -rf "$WORK_DIR"
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
restore_previous() {
|
||||
say "Setup failed; restoring the previous nginx configuration and assets."
|
||||
set +e
|
||||
|
||||
if [ "$SITE_EXISTED" -eq 1 ]; then
|
||||
run_root cp "$SITE_BACKUP" "$SITE_CONFIG"
|
||||
else
|
||||
run_root rm -f "$SITE_CONFIG"
|
||||
fi
|
||||
|
||||
if [ "$MAIN_CHANGED" -eq 1 ]; then
|
||||
run_root cp "$MAIN_BACKUP" "$NGINX_CONF"
|
||||
fi
|
||||
|
||||
for asset in autoindex.css autoindex.js favicon.ico; do
|
||||
if [ -f "$WORK_DIR/assets-backup/$asset" ]; then
|
||||
run_root cp "$WORK_DIR/assets-backup/$asset" "$ASSETS_PATH/$asset"
|
||||
else
|
||||
run_root rm -f "$ASSETS_PATH/$asset"
|
||||
fi
|
||||
done
|
||||
|
||||
if ! run_root "$NGINX_BIN" -t; then
|
||||
printf '%s\n' "nais setup: rollback completed, but the restored nginx configuration is invalid" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
trap 'exit 1' HUP INT TERM
|
||||
|
||||
if ! ( : </dev/tty && : >/dev/tty ) 2>/dev/null; then
|
||||
fail "/dev/tty is required for interactive setup"
|
||||
fi
|
||||
need_command curl
|
||||
need_command nginx
|
||||
need_command awk
|
||||
need_command sed
|
||||
need_command mktemp
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
need_command sudo
|
||||
sudo -v || fail "root privileges are required to configure nginx"
|
||||
fi
|
||||
|
||||
SERVER_NAME=$(prompt "Server name")
|
||||
[ -n "$SERVER_NAME" ] || fail "server_name is required"
|
||||
case "$SERVER_NAME" in
|
||||
*[!A-Za-z0-9._:-]*) fail "server_name must be one hostname or IP address without whitespace" ;;
|
||||
esac
|
||||
|
||||
ROOT_PATH=$(prompt "Root path")
|
||||
[ -n "$ROOT_PATH" ] || fail "root_path is required"
|
||||
validate_absolute_path "$ROOT_PATH" "root_path"
|
||||
|
||||
ASSETS_PATH=$(prompt "Assets path" "$DEFAULT_ASSETS_PATH")
|
||||
validate_absolute_path "$ASSETS_PATH" "assets_path"
|
||||
|
||||
CERTIFICATES_PATH=$(prompt "Certificates path (optional)")
|
||||
if [ -n "$CERTIFICATES_PATH" ]; then
|
||||
validate_absolute_path "$CERTIFICATES_PATH" "certificates_path"
|
||||
run_root test -f "$CERTIFICATES_PATH/fullchain.pem" ||
|
||||
fail "certificate not found: $CERTIFICATES_PATH/fullchain.pem"
|
||||
run_root test -f "$CERTIFICATES_PATH/privkey.pem" ||
|
||||
fail "certificate key not found: $CERTIFICATES_PATH/privkey.pem"
|
||||
fi
|
||||
|
||||
NGINX_BIN=$(command -v nginx)
|
||||
NGINX_VERSION=$("$NGINX_BIN" -V 2>&1)
|
||||
NGINX_PREFIX=$(printf '%s\n' "$NGINX_VERSION" | sed -n 's/.*--prefix=\([^ ]*\).*/\1/p')
|
||||
NGINX_CONF=$(printf '%s\n' "$NGINX_VERSION" | sed -n 's/.*--conf-path=\([^ ]*\).*/\1/p')
|
||||
[ -n "$NGINX_PREFIX" ] || NGINX_PREFIX=/etc/nginx
|
||||
[ -n "$NGINX_CONF" ] || NGINX_CONF=$NGINX_PREFIX/conf/nginx.conf
|
||||
|
||||
case "$NGINX_CONF" in
|
||||
/*) ;;
|
||||
*) NGINX_CONF=$NGINX_PREFIX/$NGINX_CONF ;;
|
||||
esac
|
||||
run_root test -f "$NGINX_CONF" || fail "nginx configuration not found: $NGINX_CONF"
|
||||
|
||||
WORK_DIR=$(mktemp -d)
|
||||
SITE_BACKUP=$WORK_DIR/site.conf.backup
|
||||
MAIN_BACKUP=$WORK_DIR/nginx.conf.backup
|
||||
GENERATED_CONFIG=$WORK_DIR/nais.conf
|
||||
MAIN_CURRENT=$WORK_DIR/nginx.conf.current
|
||||
MAIN_CHANGED=0
|
||||
MAIN_NEEDS_CHANGE=0
|
||||
SITE_EXISTED=0
|
||||
TRANSACTION_ACTIVE=0
|
||||
run_root cp "$NGINX_CONF" "$MAIN_CURRENT"
|
||||
run_root chmod a+r "$MAIN_CURRENT"
|
||||
|
||||
say "Downloading NAIS assets..."
|
||||
for asset in autoindex.css autoindex.js favicon.ico; do
|
||||
curl -fsSL "$BASE_URL/$asset" -o "$WORK_DIR/$asset" ||
|
||||
fail "failed to download $BASE_URL/$asset"
|
||||
done
|
||||
|
||||
ROOT_QUOTED=$(nginx_quote "$ROOT_PATH")
|
||||
ASSETS_QUOTED=$(nginx_quote "$ASSETS_PATH")
|
||||
|
||||
if [ -n "$CERTIFICATES_PATH" ]; then
|
||||
CERT_QUOTED=$(nginx_quote "$CERTIFICATES_PATH/fullchain.pem")
|
||||
KEY_QUOTED=$(nginx_quote "$CERTIFICATES_PATH/privkey.pem")
|
||||
cat >"$GENERATED_CONFIG" <<EOF
|
||||
# Managed by NAIS setup.sh. Re-running setup replaces this file.
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name $SERVER_NAME;
|
||||
|
||||
ssl_certificate $CERT_QUOTED;
|
||||
ssl_certificate_key $KEY_QUOTED;
|
||||
|
||||
root $ROOT_QUOTED;
|
||||
|
||||
location / {
|
||||
autoindex on;
|
||||
autoindex_exact_size off;
|
||||
autoindex_localtime on;
|
||||
sub_filter '</head>' '<link rel="stylesheet" href="/autoindex.css">';
|
||||
sub_filter '</body>' '<script src="/autoindex.js"></script></body>';
|
||||
sub_filter_once on;
|
||||
}
|
||||
|
||||
location = /autoindex.css { root $ASSETS_QUOTED; }
|
||||
location = /autoindex.js { root $ASSETS_QUOTED; }
|
||||
location = /favicon.ico { root $ASSETS_QUOTED; }
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name $SERVER_NAME;
|
||||
return 301 https://$SERVER_NAME\$request_uri;
|
||||
}
|
||||
EOF
|
||||
else
|
||||
cat >"$GENERATED_CONFIG" <<EOF
|
||||
# Managed by NAIS setup.sh. Re-running setup replaces this file.
|
||||
server {
|
||||
listen 80;
|
||||
server_name $SERVER_NAME;
|
||||
root $ROOT_QUOTED;
|
||||
|
||||
location / {
|
||||
autoindex on;
|
||||
autoindex_exact_size off;
|
||||
autoindex_localtime on;
|
||||
sub_filter '</head>' '<link rel="stylesheet" href="/autoindex.css">';
|
||||
sub_filter '</body>' '<script src="/autoindex.js"></script></body>';
|
||||
sub_filter_once on;
|
||||
}
|
||||
|
||||
location = /autoindex.css { root $ASSETS_QUOTED; }
|
||||
location = /autoindex.js { root $ASSETS_QUOTED; }
|
||||
location = /favicon.ico { root $ASSETS_QUOTED; }
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
ACTIVE_INCLUDE=$(
|
||||
sed 's/[[:space:]]*#.*$//' "$MAIN_CURRENT" |
|
||||
awk '
|
||||
$1 == "include" {
|
||||
path = $2
|
||||
sub(/;$/, "", path)
|
||||
gsub(/^"|"$/, "", path)
|
||||
if (path ~ /sites-enabled\/\*$/) { print path; exit }
|
||||
if (path ~ /conf\.d\/\*\.conf$/) { print path; exit }
|
||||
if (path ~ /http\.d\/\*\.conf$/) { print path; exit }
|
||||
if (path ~ /nais-sites\/\*\.conf$/) { print path; exit }
|
||||
}
|
||||
'
|
||||
)
|
||||
|
||||
if [ -n "$ACTIVE_INCLUDE" ]; then
|
||||
case "$ACTIVE_INCLUDE" in
|
||||
/*) INCLUDE_PATTERN=$ACTIVE_INCLUDE ;;
|
||||
*) INCLUDE_PATTERN=$NGINX_PREFIX/$ACTIVE_INCLUDE ;;
|
||||
esac
|
||||
SITE_DIR=${INCLUDE_PATTERN%/*}
|
||||
else
|
||||
SITE_DIR=$NGINX_PREFIX/nais-sites
|
||||
cp "$MAIN_CURRENT" "$MAIN_BACKUP"
|
||||
awk -v include_line=" $MANAGED_INCLUDE" '
|
||||
BEGIN { added = 0 }
|
||||
!added && $0 ~ /^[[:space:]]*http[[:space:]]*\{/ {
|
||||
print
|
||||
print include_line
|
||||
added = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (!added) exit 42
|
||||
}
|
||||
' "$MAIN_CURRENT" >"$WORK_DIR/nginx.conf" ||
|
||||
fail "could not find the nginx http block in $NGINX_CONF"
|
||||
MAIN_NEEDS_CHANGE=1
|
||||
fi
|
||||
|
||||
SITE_CONFIG=$SITE_DIR/nais.conf
|
||||
run_root mkdir -p "$ROOT_PATH" "$ASSETS_PATH" "$SITE_DIR"
|
||||
mkdir -p "$WORK_DIR/assets-backup"
|
||||
|
||||
for asset in autoindex.css autoindex.js favicon.ico; do
|
||||
if run_root test -e "$ASSETS_PATH/$asset" && ! run_root test -f "$ASSETS_PATH/$asset"; then
|
||||
fail "asset destination is not a regular file: $ASSETS_PATH/$asset"
|
||||
fi
|
||||
if run_root test -f "$ASSETS_PATH/$asset"; then
|
||||
run_root cp "$ASSETS_PATH/$asset" "$WORK_DIR/assets-backup/$asset"
|
||||
fi
|
||||
done
|
||||
|
||||
if run_root test -e "$SITE_CONFIG"; then
|
||||
run_root test -f "$SITE_CONFIG" || fail "site config destination is not a regular file: $SITE_CONFIG"
|
||||
run_root cp "$SITE_CONFIG" "$SITE_BACKUP"
|
||||
SITE_EXISTED=1
|
||||
fi
|
||||
|
||||
TRANSACTION_ACTIVE=1
|
||||
run_root chmod a+rx "$ASSETS_PATH"
|
||||
for asset in autoindex.css autoindex.js favicon.ico; do
|
||||
run_root cp "$WORK_DIR/$asset" "$ASSETS_PATH/$asset"
|
||||
run_root chmod a+r "$ASSETS_PATH/$asset"
|
||||
done
|
||||
run_root cp "$GENERATED_CONFIG" "$SITE_CONFIG"
|
||||
if [ "$MAIN_NEEDS_CHANGE" -eq 1 ]; then
|
||||
MAIN_CHANGED=1
|
||||
run_root cp "$WORK_DIR/nginx.conf" "$NGINX_CONF"
|
||||
fi
|
||||
|
||||
say "Validating nginx configuration..."
|
||||
if ! run_root "$NGINX_BIN" -t; then
|
||||
fail "nginx validation failed"
|
||||
fi
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
if run_root systemctl is-active --quiet nginx; then
|
||||
run_root systemctl reload nginx
|
||||
else
|
||||
run_root systemctl enable --now nginx
|
||||
fi
|
||||
elif command -v service >/dev/null 2>&1; then
|
||||
run_root service nginx restart
|
||||
else
|
||||
fail "nginx configuration is installed, but no supported service manager was found"
|
||||
fi
|
||||
|
||||
TRANSACTION_ACTIVE=0
|
||||
say "NAIS configured successfully."
|
||||
say "Site config: $SITE_CONFIG"
|
||||
say "Assets: $ASSETS_PATH"
|
||||
Reference in New Issue
Block a user