Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a34e083c2e | |||
| 52d6afa335 | |||
| ceaa684c74 | |||
| cd226f3ce9 | |||
| f4e39c96fd | |||
| c49f28e619 | |||
| b58bcd8398 | |||
| a54b0a8f8e | |||
| 65426a6c67 | |||
| 6143d9afef | |||
| 690631ef9b | |||
| dfd6d33142 | |||
| c14c032081 | |||
| 487dcea5c1 | |||
| 0d9c92971c |
@@ -0,0 +1,9 @@
|
|||||||
|
# Default: normalize all text files to LF on commit
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# Force LF for files that must be LF on Linux hosts
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.service text eol=lf
|
||||||
|
*.conf text eol=lf
|
||||||
@@ -9,7 +9,6 @@ on:
|
|||||||
- 'DeepDrftContent/**'
|
- 'DeepDrftContent/**'
|
||||||
- 'DeepDrftModels/**'
|
- 'DeepDrftModels/**'
|
||||||
- '.gitea/workflows/deploy-api.yml'
|
- '.gitea/workflows/deploy-api.yml'
|
||||||
- 'deploy/systemd/deepdrftapi.service'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -45,14 +44,6 @@ jobs:
|
|||||||
--no-build \
|
--no-build \
|
||||||
-o DeepDrftAPI/publish
|
-o DeepDrftAPI/publish
|
||||||
|
|
||||||
# DeepDrftContextFactory reads environment/connections.json at design time.
|
|
||||||
# Write a parseable dummy so the factory does not throw during bundle construction.
|
|
||||||
# The bundle only needs the provider type, not a live database connection.
|
|
||||||
- name: Write dummy connections file for EF bundle
|
|
||||||
run: |
|
|
||||||
mkdir -p DeepDrftAPI/environment
|
|
||||||
echo '{"ConnectionStrings":{"DefaultConnection":"Host=localhost;Database=dummy;Username=dummy","Auth":"Host=localhost;Database=dummy;Username=dummy"}}' > DeepDrftAPI/environment/connections.json
|
|
||||||
|
|
||||||
# EF bundle: self-contained binary that applies DeepDrftContext migrations on the host
|
# EF bundle: self-contained binary that applies DeepDrftContext migrations on the host
|
||||||
# without the .NET SDK. AuthBlocks' Identity DB is NOT covered here — it self-migrates
|
# without the .NET SDK. AuthBlocks' Identity DB is NOT covered here — it self-migrates
|
||||||
# via UseAuthBlocksStartupAsync() on first boot.
|
# via UseAuthBlocksStartupAsync() on first boot.
|
||||||
@@ -71,7 +62,7 @@ jobs:
|
|||||||
run: tar -czf deepdrft-api.tar.gz -C DeepDrftAPI/publish .
|
run: tar -czf deepdrft-api.tar.gz -C DeepDrftAPI/publish .
|
||||||
|
|
||||||
- name: Upload artifacts (archive + bundle)
|
- name: Upload artifacts (archive + bundle)
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: deepdrft-api
|
name: deepdrft-api
|
||||||
path: |
|
path: |
|
||||||
@@ -90,7 +81,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: deepdrft-api
|
name: deepdrft-api
|
||||||
path: staging/
|
path: staging/
|
||||||
@@ -118,7 +109,6 @@ jobs:
|
|||||||
rsync -e "ssh -i ~/.ssh/deepdrft_ed25519 -o StrictHostKeyChecking=yes" \
|
rsync -e "ssh -i ~/.ssh/deepdrft_ed25519 -o StrictHostKeyChecking=yes" \
|
||||||
staging/deepdrft-api.tar.gz \
|
staging/deepdrft-api.tar.gz \
|
||||||
staging/deepdrft-migrations-bundle \
|
staging/deepdrft-migrations-bundle \
|
||||||
deploy/systemd/deepdrftapi.service \
|
|
||||||
deepdrft@$DEPLOY_HOST:
|
deepdrft@$DEPLOY_HOST:
|
||||||
|
|
||||||
- name: Trigger deploy on host
|
- name: Trigger deploy on host
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
run: tar -czf deepdrft-manager.tar.gz -C DeepDrftManager/publish .
|
run: tar -czf deepdrft-manager.tar.gz -C DeepDrftManager/publish .
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: deepdrft-manager
|
name: deepdrft-manager
|
||||||
path: deepdrft-manager.tar.gz
|
path: deepdrft-manager.tar.gz
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
DEPLOY_HOST: ${{ github.ref == 'refs/heads/master' && 'prod.cerebellumsoftworks.com' || 'dch7.cerebellumsoftworks.com' }}
|
DEPLOY_HOST: ${{ github.ref == 'refs/heads/master' && 'prod.cerebellumsoftworks.com' || 'dch7.cerebellumsoftworks.com' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: deepdrft-manager
|
name: deepdrft-manager
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
run: tar -czf deepdrft-public.tar.gz -C DeepDrftPublic/publish .
|
run: tar -czf deepdrft-public.tar.gz -C DeepDrftPublic/publish .
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: deepdrft-public
|
name: deepdrft-public
|
||||||
path: deepdrft-public.tar.gz
|
path: deepdrft-public.tar.gz
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
DEPLOY_HOST: ${{ github.ref == 'refs/heads/master' && 'prod.cerebellumsoftworks.com' || 'dch7.cerebellumsoftworks.com' }}
|
DEPLOY_HOST: ${{ github.ref == 'refs/heads/master' && 'prod.cerebellumsoftworks.com' || 'dch7.cerebellumsoftworks.com' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: deepdrft-public
|
name: deepdrft-public
|
||||||
|
|
||||||
|
|||||||
@@ -26,3 +26,4 @@
|
|||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
using NetBlocks.Utilities.Environment;
|
||||||
|
|
||||||
namespace DeepDrftData.Data;
|
namespace DeepDrftData.Data;
|
||||||
|
|
||||||
@@ -7,23 +8,21 @@ public class DeepDrftContextFactory : IDesignTimeDbContextFactory<DeepDrftContex
|
|||||||
{
|
{
|
||||||
public DeepDrftContext CreateDbContext(string[] args)
|
public DeepDrftContext CreateDbContext(string[] args)
|
||||||
{
|
{
|
||||||
// Load the real connection string from environment/connections.json — the same
|
var path = CredentialTools.ResolvePath("connections", "environment/connections.json");
|
||||||
// file DeepDrftPublic's Program.cs loads via CredentialTools. When EF tools run with
|
|
||||||
// --startup-project DeepDrftPublic, the working directory resolves there, so this
|
|
||||||
// relative path works without any env var configuration.
|
|
||||||
const string relPath = "environment/connections.json";
|
|
||||||
if (!File.Exists(relPath))
|
|
||||||
throw new FileNotFoundException(
|
|
||||||
$"'{relPath}' not found. Run EF commands with --startup-project DeepDrftPublic " +
|
|
||||||
$"from the solution root (current dir: {Directory.GetCurrentDirectory()}).", relPath);
|
|
||||||
|
|
||||||
using var doc = System.Text.Json.JsonDocument.Parse(File.ReadAllText(relPath));
|
string? connectionString = null;
|
||||||
var connectionString = doc.RootElement
|
if (File.Exists(path))
|
||||||
.GetProperty("ConnectionStrings")
|
{
|
||||||
.GetProperty("DefaultConnection")
|
using var doc = System.Text.Json.JsonDocument.Parse(File.ReadAllText(path));
|
||||||
.GetString()
|
connectionString = doc.RootElement
|
||||||
?? throw new InvalidOperationException(
|
.GetProperty("ConnectionStrings")
|
||||||
"ConnectionStrings:DefaultConnection not found in environment/connections.json");
|
.GetProperty("DefaultConnection")
|
||||||
|
.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to a design-time dummy — the bundle only needs the provider/schema,
|
||||||
|
// not a live connection. This removes the requirement to write a dummy file in CI.
|
||||||
|
connectionString ??= "Host=localhost;Database=deepdrft-design-time;Username=dummy";
|
||||||
|
|
||||||
var optionsBuilder = new DbContextOptionsBuilder<DeepDrftContext>();
|
var optionsBuilder = new DbContextOptionsBuilder<DeepDrftContext>();
|
||||||
optionsBuilder.UseNpgsql(connectionString);
|
optionsBuilder.UseNpgsql(connectionString);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<!-- Npgsql 10.0.1 requires Microsoft.EntityFrameworkCore >= 10.0.4; keep in sync -->
|
<!-- Npgsql 10.0.1 requires Microsoft.EntityFrameworkCore >= 10.0.4; keep in sync -->
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Cerebellum.NetBlocks" Version="10.3.30" />
|
||||||
<PackageReference Include="Cerebellum.BlazorBlocks.Data" Version="10.3.30" />
|
<PackageReference Include="Cerebellum.BlazorBlocks.Data" Version="10.3.30" />
|
||||||
<PackageReference Include="Cerebellum.BlazorBlocks.Data.Postgres" Version="10.3.30" />
|
<PackageReference Include="Cerebellum.BlazorBlocks.Data.Postgres" Version="10.3.30" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -17,3 +17,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="hero-eyebrow fade-up">Charleston, South Carolina</div>
|
||||||
|
<h1 class="hero-title fade-up">Deep<br /><em>Drft</em></h1>
|
||||||
|
<p class="hero-subtitle fade-up">Electronic Music Collective</p>
|
||||||
|
<p class="hero-desc fade-up">
|
||||||
|
We craft immersive electronic soundscapes — live; built from synthesizers, drum machines, and raw intention.
|
||||||
|
</p>
|
||||||
|
<div class="hero-actions fade-up">
|
||||||
|
<a class="btn-primary" href="/tracks">Start Streaming</a>
|
||||||
|
<a class="btn-ghost" href="/tracks">Browse Tracks</a>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/* ── Animations ── */
|
||||||
|
@keyframes fade-up {
|
||||||
|
from { opacity: 0; transform: translateY(24px); }
|
||||||
|
to { opacity: 1; transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-eyebrow {
|
||||||
|
font-family: var(--deepdrft-font-mono);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
letter-spacing: 0.28em;
|
||||||
|
color: var(--deepdrft-green-accent);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 1.8rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
animation-delay: 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-eyebrow::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--deepdrft-green-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-family: var(--deepdrft-font-display);
|
||||||
|
font-size: clamp(4.5rem, 8vw, 8.5rem);
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 0.92;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: var(--deepdrft-navy);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
animation-delay: 0.22s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title em {
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--deepdrft-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle {
|
||||||
|
font-family: var(--deepdrft-font-display);
|
||||||
|
font-size: clamp(1rem, 2vw, 1.35rem);
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--deepdrft-muted);
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
animation-delay: 0.34s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-desc {
|
||||||
|
font-family: var(--deepdrft-font-body);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
line-height: 1.75;
|
||||||
|
color: var(--deepdrft-navy);
|
||||||
|
opacity: 0.7;
|
||||||
|
max-width: 36ch;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
animation-delay: 0.44s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
animation-delay: 0.54s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
font-family: var(--deepdrft-font-mono);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--deepdrft-white);
|
||||||
|
background: var(--deepdrft-navy);
|
||||||
|
border: none;
|
||||||
|
padding: 1rem 2.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.25s, transform 0.2s;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--deepdrft-green);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost {
|
||||||
|
font-family: var(--deepdrft-font-mono);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--deepdrft-navy);
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--deepdrft-border);
|
||||||
|
padding: 1rem 2.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-color 0.25s, color 0.25s;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost:hover { border-color: var(--deepdrft-navy); }
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
@* Pulsing rings *@
|
||||||
|
<div class="circle-deco"></div>
|
||||||
|
<div class="circle-deco"></div>
|
||||||
|
<div class="circle-deco"></div>
|
||||||
|
|
||||||
|
<div class="now-playing-content">
|
||||||
|
<NowPlayingCard />
|
||||||
|
|
||||||
|
@* Stat row - hard-coded for now. TODO Phase 2: wire to real track count / identity model. *@
|
||||||
|
<NowPlayingStats />
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.now-playing-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-deco {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(61, 122, 104, 0.3);
|
||||||
|
animation: pulse-ring 4s ease-in-out infinite;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-deco:nth-child(1) { width: 320px; height: 320px; animation-delay: 0s; }
|
||||||
|
.circle-deco:nth-child(2) { width: 520px; height: 520px; animation-delay: 0.8s; }
|
||||||
|
.circle-deco:nth-child(3) { width: 720px; height: 720px; animation-delay: 1.6s; }
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
@using DeepDrftPublic.Client.Services
|
||||||
|
<div class="now-playing">
|
||||||
|
<div class="np-label"><span class="np-dot"></span>Now Playing</div>
|
||||||
|
<div class="np-title">@(Player?.CurrentTrack?.TrackName ?? "Nothing playing")</div>
|
||||||
|
<div class="np-sub">
|
||||||
|
@(Player?.CurrentTrack != null
|
||||||
|
? $"{Player.CurrentTrack.Artist} · {Player.CurrentTrack.Album ?? "Single"}"
|
||||||
|
: "Select a track to begin")
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Player?.IsLoaded == true)
|
||||||
|
{
|
||||||
|
<div class="waveform-bars">
|
||||||
|
@* 20 bars - approximate the wireframe's varied animation timings *@
|
||||||
|
<div class="waveform-bar" style="--h-lo:6px;--h-hi:28px;--dur:0.7s;height:14px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:10px;--h-hi:36px;--dur:0.9s;height:28px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:4px;--h-hi:20px;--dur:0.65s;height:10px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:12px;--h-hi:40px;--dur:1.1s;height:36px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:6px;--h-hi:26px;--dur:0.8s;height:18px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:8px;--h-hi:32px;--dur:0.75s;height:24px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:4px;--h-hi:18px;--dur:0.95s;height:8px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:14px;--h-hi:42px;--dur:1.2s;height:32px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:6px;--h-hi:22px;--dur:0.68s;height:16px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:10px;--h-hi:38px;--dur:0.88s;height:30px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:4px;--h-hi:16px;--dur:0.72s;height:6px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:8px;--h-hi:30px;--dur:1.0s;height:20px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:12px;--h-hi:36px;--dur:0.85s;height:26px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:6px;--h-hi:24px;--dur:0.9s;height:14px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:10px;--h-hi:34px;--dur:0.78s;height:22px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:4px;--h-hi:20px;--dur:1.05s;height:12px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:14px;--h-hi:44px;--dur:0.92s;height:38px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:6px;--h-hi:26px;--dur:0.7s;height:18px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:8px;--h-hi:32px;--dur:0.82s;height:22px"></div>
|
||||||
|
<div class="waveform-bar" style="--h-lo:4px;--h-hi:18px;--dur:1.15s;height:10px"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="waveform-placeholder"></div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[CascadingParameter] public IStreamingPlayerService? Player { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
.now-playing {
|
||||||
|
background: rgba(250, 250, 248, 0.06);
|
||||||
|
border: 1px solid rgba(250, 250, 248, 0.12);
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.np-label {
|
||||||
|
font-family: var(--deepdrft-font-mono);
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 0.25em;
|
||||||
|
color: var(--deepdrft-green-accent);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.np-dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--deepdrft-green-accent);
|
||||||
|
animation: blink 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.np-title {
|
||||||
|
font-family: var(--deepdrft-font-display);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--deepdrft-white);
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.np-sub {
|
||||||
|
font-family: var(--deepdrft-font-body);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: rgba(250, 250, 248, 0.45);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waveform-bars {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
margin-top: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waveform-bar {
|
||||||
|
width: 3px;
|
||||||
|
background: var(--deepdrft-green-accent);
|
||||||
|
border-radius: 2px;
|
||||||
|
animation: wave-dance var(--dur, 1s) ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waveform-placeholder {
|
||||||
|
margin-top: 1.2rem;
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(250, 250, 248, 0.12);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<div class="hero-stat-row">
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="hero-stat-num">47+</div>
|
||||||
|
<div class="hero-stat-label">Live Sessions</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="hero-stat-num">2</div>
|
||||||
|
<div class="hero-stat-label">Members</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-stat">
|
||||||
|
<div class="hero-stat-num">∞</div>
|
||||||
|
<div class="hero-stat-label">Drift Points</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
.hero-stat-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat {
|
||||||
|
flex: 1;
|
||||||
|
background: rgba(250, 250, 248, 0.04);
|
||||||
|
border: 1px solid rgba(250, 250, 248, 0.08);
|
||||||
|
padding: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat-num {
|
||||||
|
font-family: var(--deepdrft-font-display);
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--deepdrft-white);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stat-label {
|
||||||
|
font-family: var(--deepdrft-font-mono);
|
||||||
|
font-size: 0.58rem;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: rgba(250, 250, 248, 0.4);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@using DeepDrftPublic.Client.Controls
|
||||||
@using DeepDrftPublic.Client.Services
|
@using DeepDrftPublic.Client.Services
|
||||||
|
|
||||||
<PageTitle>Deep DRFT - Electronic Music Collective</PageTitle>
|
<PageTitle>Deep DRFT - Electronic Music Collective</PageTitle>
|
||||||
@@ -6,83 +7,11 @@
|
|||||||
@* Hero - split 50/50 *@
|
@* Hero - split 50/50 *@
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="hero-left">
|
<div class="hero-left">
|
||||||
<div class="hero-eyebrow fade-up">Charleston, South Carolina</div>
|
<DeepDrftHero />
|
||||||
<h1 class="hero-title fade-up">Deep<br /><em>Drft</em></h1>
|
|
||||||
<p class="hero-subtitle fade-up">Electronic Music Collective</p>
|
|
||||||
<p class="hero-desc fade-up">
|
|
||||||
We craft immersive electronic soundscapes — live, unscripted, and built from synthesizers, drum machines, and raw intention. No sets. No loops. Pure drift.
|
|
||||||
</p>
|
|
||||||
<div class="hero-actions fade-up">
|
|
||||||
<a class="btn-primary" href="/tracks">Start Streaming</a>
|
|
||||||
<a class="btn-ghost" href="/tracks">Browse Tracks</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hero-right">
|
<div class="hero-right">
|
||||||
@* Pulsing rings *@
|
<NowPlaying />
|
||||||
<div class="circle-deco"></div>
|
|
||||||
<div class="circle-deco"></div>
|
|
||||||
<div class="circle-deco"></div>
|
|
||||||
|
|
||||||
<div class="hero-right-content">
|
|
||||||
@* Now-Playing card *@
|
|
||||||
<div class="now-playing">
|
|
||||||
<div class="np-label"><span class="np-dot"></span>Now Playing</div>
|
|
||||||
<div class="np-title">@(Player?.CurrentTrack?.TrackName ?? "Nothing playing")</div>
|
|
||||||
<div class="np-sub">
|
|
||||||
@(Player?.CurrentTrack != null
|
|
||||||
? $"{Player.CurrentTrack.Artist} · {Player.CurrentTrack.Album ?? "Single"}"
|
|
||||||
: "Select a track to begin")
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (Player?.IsLoaded == true)
|
|
||||||
{
|
|
||||||
<div class="waveform-bars">
|
|
||||||
@* 20 bars - approximate the wireframe's varied animation timings *@
|
|
||||||
<div class="waveform-bar" style="--h-lo:6px;--h-hi:28px;--dur:0.7s;height:14px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:10px;--h-hi:36px;--dur:0.9s;height:28px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:4px;--h-hi:20px;--dur:0.65s;height:10px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:12px;--h-hi:40px;--dur:1.1s;height:36px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:6px;--h-hi:26px;--dur:0.8s;height:18px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:8px;--h-hi:32px;--dur:0.75s;height:24px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:4px;--h-hi:18px;--dur:0.95s;height:8px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:14px;--h-hi:42px;--dur:1.2s;height:32px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:6px;--h-hi:22px;--dur:0.68s;height:16px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:10px;--h-hi:38px;--dur:0.88s;height:30px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:4px;--h-hi:16px;--dur:0.72s;height:6px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:8px;--h-hi:30px;--dur:1.0s;height:20px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:12px;--h-hi:36px;--dur:0.85s;height:26px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:6px;--h-hi:24px;--dur:0.9s;height:14px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:10px;--h-hi:34px;--dur:0.78s;height:22px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:4px;--h-hi:20px;--dur:1.05s;height:12px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:14px;--h-hi:44px;--dur:0.92s;height:38px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:6px;--h-hi:26px;--dur:0.7s;height:18px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:8px;--h-hi:32px;--dur:0.82s;height:22px"></div>
|
|
||||||
<div class="waveform-bar" style="--h-lo:4px;--h-hi:18px;--dur:1.15s;height:10px"></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="waveform-placeholder"></div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Stat row - hard-coded for now. TODO Phase 2: wire to real track count / identity model. *@
|
|
||||||
<div class="hero-stat-row">
|
|
||||||
<div class="hero-stat">
|
|
||||||
<div class="hero-stat-num">47+</div>
|
|
||||||
<div class="hero-stat-label">Live Sessions</div>
|
|
||||||
</div>
|
|
||||||
<div class="hero-stat">
|
|
||||||
<div class="hero-stat-num">2</div>
|
|
||||||
<div class="hero-stat-label">Members</div>
|
|
||||||
</div>
|
|
||||||
<div class="hero-stat">
|
|
||||||
<div class="hero-stat-num">∞</div>
|
|
||||||
<div class="hero-stat-label">Drift Points</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -43,109 +43,6 @@
|
|||||||
background: var(--deepdrft-white);
|
background: var(--deepdrft-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-eyebrow {
|
|
||||||
font-family: var(--deepdrft-font-mono);
|
|
||||||
font-size: 0.65rem;
|
|
||||||
letter-spacing: 0.28em;
|
|
||||||
color: var(--deepdrft-green-accent);
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: 1.8rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-eyebrow::before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
width: 2.5rem;
|
|
||||||
height: 1px;
|
|
||||||
background: var(--deepdrft-green-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title {
|
|
||||||
font-family: var(--deepdrft-font-display);
|
|
||||||
font-size: clamp(4.5rem, 8vw, 8.5rem);
|
|
||||||
font-weight: 300;
|
|
||||||
line-height: 0.92;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
color: var(--deepdrft-navy);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
animation-delay: 0.22s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title em {
|
|
||||||
font-style: italic;
|
|
||||||
color: var(--deepdrft-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-subtitle {
|
|
||||||
font-family: var(--deepdrft-font-display);
|
|
||||||
font-size: clamp(1rem, 2vw, 1.35rem);
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: italic;
|
|
||||||
color: var(--deepdrft-muted);
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
animation-delay: 0.34s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-desc {
|
|
||||||
font-family: var(--deepdrft-font-body);
|
|
||||||
font-size: 0.92rem;
|
|
||||||
line-height: 1.75;
|
|
||||||
color: var(--deepdrft-navy);
|
|
||||||
opacity: 0.7;
|
|
||||||
max-width: 36ch;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
animation-delay: 0.44s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
animation-delay: 0.54s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
font-family: var(--deepdrft-font-mono);
|
|
||||||
font-size: 0.68rem;
|
|
||||||
letter-spacing: 0.2em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--deepdrft-white);
|
|
||||||
background: var(--deepdrft-navy);
|
|
||||||
border: none;
|
|
||||||
padding: 1rem 2.2rem;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: background 0.25s, transform 0.2s;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: var(--deepdrft-green);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost {
|
|
||||||
font-family: var(--deepdrft-font-mono);
|
|
||||||
font-size: 0.68rem;
|
|
||||||
letter-spacing: 0.2em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--deepdrft-navy);
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--deepdrft-border);
|
|
||||||
padding: 1rem 2.2rem;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: border-color 0.25s, color 0.25s;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost:hover { border-color: var(--deepdrft-navy); }
|
|
||||||
|
|
||||||
.hero-right {
|
.hero-right {
|
||||||
background: var(--deepdrft-navy);
|
background: var(--deepdrft-navy);
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -155,119 +52,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.circle-deco {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid rgba(61, 122, 104, 0.3);
|
|
||||||
animation: pulse-ring 4s ease-in-out infinite;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle-deco:nth-child(1) { width: 320px; height: 320px; animation-delay: 0s; }
|
|
||||||
.circle-deco:nth-child(2) { width: 520px; height: 520px; animation-delay: 0.8s; }
|
|
||||||
.circle-deco:nth-child(3) { width: 720px; height: 720px; animation-delay: 1.6s; }
|
|
||||||
|
|
||||||
.hero-right-content {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
padding: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.now-playing {
|
|
||||||
background: rgba(250, 250, 248, 0.06);
|
|
||||||
border: 1px solid rgba(250, 250, 248, 0.12);
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
backdrop-filter: blur(8px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.np-label {
|
|
||||||
font-family: var(--deepdrft-font-mono);
|
|
||||||
font-size: 0.6rem;
|
|
||||||
letter-spacing: 0.25em;
|
|
||||||
color: var(--deepdrft-green-accent);
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.np-dot {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--deepdrft-green-accent);
|
|
||||||
animation: blink 1.2s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.np-title {
|
|
||||||
font-family: var(--deepdrft-font-display);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--deepdrft-white);
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.np-sub {
|
|
||||||
font-family: var(--deepdrft-font-body);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: rgba(250, 250, 248, 0.45);
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.waveform-bars {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 3px;
|
|
||||||
margin-top: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.waveform-bar {
|
|
||||||
width: 3px;
|
|
||||||
background: var(--deepdrft-green-accent);
|
|
||||||
border-radius: 2px;
|
|
||||||
animation: wave-dance var(--dur, 1s) ease-in-out infinite alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
.waveform-placeholder {
|
|
||||||
margin-top: 1.2rem;
|
|
||||||
height: 1px;
|
|
||||||
background: rgba(250, 250, 248, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-stat-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-stat {
|
|
||||||
flex: 1;
|
|
||||||
background: rgba(250, 250, 248, 0.04);
|
|
||||||
border: 1px solid rgba(250, 250, 248, 0.08);
|
|
||||||
padding: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-stat-num {
|
|
||||||
font-family: var(--deepdrft-font-display);
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 300;
|
|
||||||
color: var(--deepdrft-white);
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-stat-label {
|
|
||||||
font-family: var(--deepdrft-font-mono);
|
|
||||||
font-size: 0.58rem;
|
|
||||||
letter-spacing: 0.2em;
|
|
||||||
color: rgba(250, 250, 248, 0.4);
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-top: 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── DIVIDER ── */
|
/* ── DIVIDER ── */
|
||||||
.section-divider {
|
.section-divider {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -120,3 +120,4 @@ fi
|
|||||||
chmod +x "${EXTRACT_DIR}/install.sh"
|
chmod +x "${EXTRACT_DIR}/install.sh"
|
||||||
exec bash "${EXTRACT_DIR}/install.sh"
|
exec bash "${EXTRACT_DIR}/install.sh"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
# Expects in ${APP_HOME}/staging/:
|
# Expects in ${APP_HOME}/staging/:
|
||||||
# deepdrft-api.tar.gz -- published self-contained linux-x64 binary tree
|
# deepdrft-api.tar.gz -- published self-contained linux-x64 binary tree
|
||||||
# deepdrft-migrations-bundle -- self-contained EF bundle (pre-built in CI)
|
# deepdrft-migrations-bundle -- self-contained EF bundle (pre-built in CI)
|
||||||
# deepdrftapi.service -- systemd unit file (optional)
|
|
||||||
#
|
#
|
||||||
# Migrations are applied BEFORE the service restarts via the EF bundle binary.
|
# Migrations are applied BEFORE the service restarts via the EF bundle binary.
|
||||||
# The bundle covers DeepDrftContext (track metadata DB) only.
|
# The bundle covers DeepDrftContext (track metadata DB) only.
|
||||||
@@ -94,15 +93,6 @@ if [[ -d "${APPROOT}/environment" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Install systemd unit file (if present in staging) ─────────────────────
|
|
||||||
if [[ -f "${STAGING}/deepdrftapi.service" ]]; then
|
|
||||||
mkdir -p "${APP_HOME}/.config/systemd/user"
|
|
||||||
cp "${STAGING}/deepdrftapi.service" "${APP_HOME}/.config/systemd/user/deepdrftapi.service"
|
|
||||||
rm -f "${STAGING}/deepdrftapi.service"
|
|
||||||
systemctl --user daemon-reload
|
|
||||||
echo "[deploy-api] systemd unit file installed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Enable and restart service ─────────────────────────────────────────────
|
# ── Enable and restart service ─────────────────────────────────────────────
|
||||||
systemctl --user enable deepdrftapi.service
|
systemctl --user enable deepdrftapi.service
|
||||||
systemctl --user restart deepdrftapi.service
|
systemctl --user restart deepdrftapi.service
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
# Expects in ${APP_HOME}/staging/:
|
# Expects in ${APP_HOME}/staging/:
|
||||||
# deepdrft-manager.tar.gz -- published self-contained linux-x64 binary tree
|
# deepdrft-manager.tar.gz -- published self-contained linux-x64 binary tree
|
||||||
#
|
#
|
||||||
# DeepDrftManager receives its API URL + API key credential via systemd LoadCredential
|
|
||||||
# (api-manager.json -> $CREDENTIALS_DIRECTORY/api at runtime). No env file copy needed.
|
|
||||||
#
|
|
||||||
# Paths are derived at runtime — no hardcoded usernames or home dirs.
|
# Paths are derived at runtime — no hardcoded usernames or home dirs.
|
||||||
# APP_HOME comes from $HOME (sshd sets this for the app user).
|
# APP_HOME comes from $HOME (sshd sets this for the app user).
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
# Expects in ${APP_HOME}/staging/:
|
# Expects in ${APP_HOME}/staging/:
|
||||||
# deepdrft-public.tar.gz -- published self-contained linux-x64 binary tree
|
# deepdrft-public.tar.gz -- published self-contained linux-x64 binary tree
|
||||||
#
|
#
|
||||||
# DeepDrftPublic receives its API URL credential via systemd LoadCredential
|
|
||||||
# (api-public.json -> $CREDENTIALS_DIRECTORY/api at runtime). No env file copy needed.
|
|
||||||
#
|
|
||||||
# Paths are derived at runtime — no hardcoded usernames or home dirs.
|
# Paths are derived at runtime — no hardcoded usernames or home dirs.
|
||||||
# APP_HOME comes from $HOME (sshd sets this for the app user).
|
# APP_HOME comes from $HOME (sshd sets this for the app user).
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,15 @@ DOMAIN_PUBLIC="${DOMAIN_PUBLIC:-deepdrft.com}"
|
|||||||
read -rp " App subdomain [app.${DOMAIN_PUBLIC}]: " DOMAIN_APP
|
read -rp " App subdomain [app.${DOMAIN_PUBLIC}]: " DOMAIN_APP
|
||||||
DOMAIN_APP="${DOMAIN_APP:-app.${DOMAIN_PUBLIC}}"
|
DOMAIN_APP="${DOMAIN_APP:-app.${DOMAIN_PUBLIC}}"
|
||||||
|
|
||||||
|
read -rp " DeepDrftPublic port [5000]: " PORT_PUBLIC
|
||||||
|
PORT_PUBLIC="${PORT_PUBLIC:-5000}"
|
||||||
|
|
||||||
|
read -rp " DeepDrftManager port [5001]: " PORT_MANAGER
|
||||||
|
PORT_MANAGER="${PORT_MANAGER:-5001}"
|
||||||
|
|
||||||
|
read -rp " DeepDrftAPI port [5002]: " PORT_API
|
||||||
|
PORT_API="${PORT_API:-5002}"
|
||||||
|
|
||||||
CERTBOT_EMAIL=""
|
CERTBOT_EMAIL=""
|
||||||
while [[ -z "${CERTBOT_EMAIL}" ]]; do
|
while [[ -z "${CERTBOT_EMAIL}" ]]; do
|
||||||
read -rp " Email for certbot TLS cert (required): " CERTBOT_EMAIL
|
read -rp " Email for certbot TLS cert (required): " CERTBOT_EMAIL
|
||||||
@@ -103,6 +112,9 @@ printf " │ %-22s %-37s│\n" "DB_AUTH" "${DB_AUTH}"
|
|||||||
printf " │ %-22s %-37s│\n" "DOMAIN_PUBLIC" "${DOMAIN_PUBLIC}"
|
printf " │ %-22s %-37s│\n" "DOMAIN_PUBLIC" "${DOMAIN_PUBLIC}"
|
||||||
printf " │ %-22s %-37s│\n" "DOMAIN_APP" "${DOMAIN_APP}"
|
printf " │ %-22s %-37s│\n" "DOMAIN_APP" "${DOMAIN_APP}"
|
||||||
printf " │ %-22s %-37s│\n" "CERTBOT_EMAIL" "${CERTBOT_EMAIL}"
|
printf " │ %-22s %-37s│\n" "CERTBOT_EMAIL" "${CERTBOT_EMAIL}"
|
||||||
|
printf " │ %-22s %-37s│\n" "PORT_PUBLIC" "${PORT_PUBLIC}"
|
||||||
|
printf " │ %-22s %-37s│\n" "PORT_MANAGER" "${PORT_MANAGER}"
|
||||||
|
printf " │ %-22s %-37s│\n" "PORT_API" "${PORT_API}"
|
||||||
printf " │ %-22s %-37s│\n" "OPT_DIR" "${OPT_DIR}"
|
printf " │ %-22s %-37s│\n" "OPT_DIR" "${OPT_DIR}"
|
||||||
echo " └──────────────────────────────────────────────────────────────┘"
|
echo " └──────────────────────────────────────────────────────────────┘"
|
||||||
echo
|
echo
|
||||||
@@ -204,6 +216,10 @@ cp "${SCRIPT_DIR}/systemd/deepdrftpublic.service" "${APP_HOME}/.config/systemd/
|
|||||||
cp "${SCRIPT_DIR}/systemd/deepdrftmanager.service" "${APP_HOME}/.config/systemd/user/"
|
cp "${SCRIPT_DIR}/systemd/deepdrftmanager.service" "${APP_HOME}/.config/systemd/user/"
|
||||||
cp "${SCRIPT_DIR}/systemd/deepdrftapi.service" "${APP_HOME}/.config/systemd/user/"
|
cp "${SCRIPT_DIR}/systemd/deepdrftapi.service" "${APP_HOME}/.config/systemd/user/"
|
||||||
|
|
||||||
|
sed -i "s|__PORT_PUBLIC__|${PORT_PUBLIC}|g" "${APP_HOME}/.config/systemd/user/deepdrftpublic.service"
|
||||||
|
sed -i "s|__PORT_MANAGER__|${PORT_MANAGER}|g" "${APP_HOME}/.config/systemd/user/deepdrftmanager.service"
|
||||||
|
sed -i "s|__PORT_API__|${PORT_API}|g" "${APP_HOME}/.config/systemd/user/deepdrftapi.service"
|
||||||
|
|
||||||
chown -R "${APP_USER}:${APP_USER}" "${APP_HOME}/.config/systemd"
|
chown -R "${APP_USER}:${APP_USER}" "${APP_HOME}/.config/systemd"
|
||||||
|
|
||||||
# daemon-reload and enable. XDG_RUNTIME_DIR must be set explicitly — PAM may not
|
# daemon-reload and enable. XDG_RUNTIME_DIR must be set explicitly — PAM may not
|
||||||
@@ -237,6 +253,7 @@ else
|
|||||||
DB_AUTH="${DB_AUTH}" \
|
DB_AUTH="${DB_AUTH}" \
|
||||||
DOMAIN_PUBLIC="${DOMAIN_PUBLIC}" \
|
DOMAIN_PUBLIC="${DOMAIN_PUBLIC}" \
|
||||||
DOMAIN_APP="${DOMAIN_APP}" \
|
DOMAIN_APP="${DOMAIN_APP}" \
|
||||||
|
PORT_API="${PORT_API}" \
|
||||||
bash "${SCRIPT_DIR}/setup-step10-creds.sh"
|
bash "${SCRIPT_DIR}/setup-step10-creds.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -371,10 +388,12 @@ step 9 "nginx"
|
|||||||
# Templates use __DOMAIN_PUBLIC__ and __DOMAIN_APP__ so the files in the tarball
|
# Templates use __DOMAIN_PUBLIC__ and __DOMAIN_APP__ so the files in the tarball
|
||||||
# don't contain real hostnames — substitution happens at install time.
|
# don't contain real hostnames — substitution happens at install time.
|
||||||
sed -e "s|__DOMAIN_PUBLIC__|${DOMAIN_PUBLIC}|g" \
|
sed -e "s|__DOMAIN_PUBLIC__|${DOMAIN_PUBLIC}|g" \
|
||||||
|
-e "s|__PORT_PUBLIC__|${PORT_PUBLIC}|g" \
|
||||||
"${SCRIPT_DIR}/nginx/deepdrft-public.conf" \
|
"${SCRIPT_DIR}/nginx/deepdrft-public.conf" \
|
||||||
> "/etc/nginx/sites-available/${DOMAIN_PUBLIC}.conf"
|
> "/etc/nginx/sites-available/${DOMAIN_PUBLIC}.conf"
|
||||||
|
|
||||||
sed -e "s|__DOMAIN_APP__|${DOMAIN_APP}|g" \
|
sed -e "s|__DOMAIN_APP__|${DOMAIN_APP}|g" \
|
||||||
|
-e "s|__PORT_MANAGER__|${PORT_MANAGER}|g" \
|
||||||
"${SCRIPT_DIR}/nginx/deepdrft-manager.conf" \
|
"${SCRIPT_DIR}/nginx/deepdrft-manager.conf" \
|
||||||
> "/etc/nginx/sites-available/${DOMAIN_APP}.conf"
|
> "/etc/nginx/sites-available/${DOMAIN_APP}.conf"
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ server {
|
|||||||
server_name __DOMAIN_APP__;
|
server_name __DOMAIN_APP__;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:5001;
|
proxy_pass http://localhost:__PORT_MANAGER__;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ server {
|
|||||||
server_name __DOMAIN_PUBLIC__;
|
server_name __DOMAIN_PUBLIC__;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:5000;
|
proxy_pass http://localhost:__PORT_PUBLIC__;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ fi
|
|||||||
# ── 5. api-public.json — no prompts, static localhost URL ────────────────────
|
# ── 5. api-public.json — no prompts, static localhost URL ────────────────────
|
||||||
if need_cred "api-public"; then
|
if need_cred "api-public"; then
|
||||||
write_cred "api-public" \
|
write_cred "api-public" \
|
||||||
'{"Api":{"ContentApiUrl":"http://localhost:5002"}}'
|
"{\"Api\":{\"ContentApiUrl\":\"http://localhost:${PORT_API:-5002}\"}}"
|
||||||
else
|
else
|
||||||
echo "[setup-step10-creds] api-public.json already exists, skipping"
|
echo "[setup-step10-creds] api-public.json already exists, skipping"
|
||||||
fi
|
fi
|
||||||
@@ -226,7 +226,7 @@ if need_cred "api-manager"; then
|
|||||||
read -rp " Enter the API key: " API_KEY
|
read -rp " Enter the API key: " API_KEY
|
||||||
fi
|
fi
|
||||||
write_cred "api-manager" \
|
write_cred "api-manager" \
|
||||||
"{\"Api\":{\"ContentApiUrl\":\"http://localhost:5002\",\"ContentApiKey\":\"$(json_escape "${API_KEY}")\"}}"
|
"{\"Api\":{\"ContentApiUrl\":\"http://localhost:${PORT_API:-5002}\",\"ContentApiKey\":\"$(json_escape "${API_KEY}")\"}}"
|
||||||
unset API_KEY
|
unset API_KEY
|
||||||
else
|
else
|
||||||
echo "[setup-step10-creds] api-manager.json already exists, skipping"
|
echo "[setup-step10-creds] api-manager.json already exists, skipping"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ ExecStart=%h/api/deepdrft/bin/DeepDrftAPI
|
|||||||
|
|
||||||
# Non-secret config — hardcoded; no plaintext file needed.
|
# Non-secret config — hardcoded; no plaintext file needed.
|
||||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||||
Environment=ASPNETCORE_URLS=http://localhost:5002
|
Environment=ASPNETCORE_URLS=http://localhost:__PORT_API__
|
||||||
|
|
||||||
# Secrets — loaded at startup into $CREDENTIALS_DIRECTORY/.
|
# Secrets — loaded at startup into $CREDENTIALS_DIRECTORY/.
|
||||||
# Files live at %h/.config/credentials/ (deepdrft:deepdrft 600).
|
# Files live at %h/.config/credentials/ (deepdrft:deepdrft 600).
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ ExecStart=%h/manager/bin/DeepDrftManager
|
|||||||
|
|
||||||
# Non-secret config — hardcoded; no plaintext file needed.
|
# Non-secret config — hardcoded; no plaintext file needed.
|
||||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||||
Environment=ASPNETCORE_URLS=http://localhost:5001
|
Environment=ASPNETCORE_URLS=http://localhost:__PORT_MANAGER__
|
||||||
|
|
||||||
# Secrets — loaded at startup into $CREDENTIALS_DIRECTORY/.
|
# Secrets — loaded at startup into $CREDENTIALS_DIRECTORY/.
|
||||||
# File lives at %h/.config/credentials/ (deepdrft:deepdrft 600).
|
# File lives at %h/.config/credentials/ (deepdrft:deepdrft 600).
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ ExecStart=%h/public/bin/DeepDrftPublic
|
|||||||
|
|
||||||
# Non-secret config — hardcoded; no plaintext file needed.
|
# Non-secret config — hardcoded; no plaintext file needed.
|
||||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||||
Environment=ASPNETCORE_URLS=http://localhost:5000
|
Environment=ASPNETCORE_URLS=http://localhost:__PORT_PUBLIC__
|
||||||
|
|
||||||
# Secrets — loaded at startup into $CREDENTIALS_DIRECTORY/.
|
# Secrets — loaded at startup into $CREDENTIALS_DIRECTORY/.
|
||||||
# File lives at %h/.config/credentials/ (deepdrft:deepdrft 600).
|
# File lives at %h/.config/credentials/ (deepdrft:deepdrft 600).
|
||||||
|
|||||||
Reference in New Issue
Block a user