Skip to content

Commit 5bfd81a

Browse files
Mark self-hosting as available (#7)
* feat: mark self-hosting as available, update docs - Remove "Coming Soon" banner from /self-hosting page - Update home page self-hosting card: remove opacity/Coming Soon label, make it a clickable link - Update quick start to use curl + openssl instead of git clone + Phoenix eval - Standardise secret generation hint to `openssl rand -base64 64` everywhere (runtime.exs, self-hosting page) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: apply formatter to heex templates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: split deploy and docker into separate workflow PRs now only show the Test job. Deploy and Docker run on push to main and releases via a dedicated deploy.yml workflow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: move home page inline scripts to app.js Inline scripts were blocked by CSP `script-src 'self'`, preventing feature card reveal, copy buttons, and install tabs from working. Moved all three to app.js with element-existence guards. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: run dashboard tests synchronously to avoid Application.put_env race async: true + Application.put_env causes flaky failures under parallel test execution in CI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fb5146a commit 5bfd81a

File tree

7 files changed

+141
-154
lines changed

7 files changed

+141
-154
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ on:
55
branches: ["main"]
66
pull_request:
77
branches: ["main"]
8-
release:
9-
types: [published]
108

119
concurrency:
1210
group: ${{ github.workflow }}-${{ github.ref }}
@@ -70,66 +68,3 @@ jobs:
7068
run: mix test
7169
env:
7270
DATABASE_URL: postgres://postgres:postgres@localhost/crit_test
73-
74-
deploy:
75-
name: Deploy to Fly.io
76-
runs-on: ubuntu-latest
77-
needs: test
78-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
79-
concurrency: deploy-group
80-
environment:
81-
name: production
82-
url: https://crit.live
83-
steps:
84-
- uses: actions/checkout@v6
85-
- uses: superfly/flyctl-actions/setup-flyctl@master
86-
- run: flyctl deploy --remote-only
87-
env:
88-
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
89-
90-
docker:
91-
name: Build and push Docker image
92-
runs-on: ubuntu-latest
93-
needs: test
94-
if: github.event_name == 'push' || github.event_name == 'release'
95-
permissions:
96-
contents: read
97-
packages: write
98-
steps:
99-
- uses: actions/checkout@v6
100-
101-
- name: Set up QEMU
102-
uses: docker/setup-qemu-action@v4
103-
104-
- name: Set up Docker Buildx
105-
uses: docker/setup-buildx-action@v4
106-
107-
- name: Log in to GHCR
108-
uses: docker/login-action@v4
109-
with:
110-
registry: ghcr.io
111-
username: ${{ github.actor }}
112-
password: ${{ secrets.GITHUB_TOKEN }}
113-
114-
- name: Extract metadata
115-
id: meta
116-
uses: docker/metadata-action@v6
117-
with:
118-
images: ghcr.io/${{ github.repository }}
119-
tags: |
120-
type=ref,event=branch
121-
type=semver,pattern={{version}}
122-
type=semver,pattern={{major}}.{{minor}}
123-
type=semver,pattern={{major}}
124-
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
125-
126-
- name: Build and push
127-
uses: docker/build-push-action@v7
128-
with:
129-
context: .
130-
platforms: linux/amd64,linux/arm64
131-
push: true
132-
tags: ${{ steps.meta.outputs.tags }}
133-
labels: ${{ steps.meta.outputs.labels }}
134-
cache-from: type=gha
135-
cache-to: type=gha,mode=max

.github/workflows/deploy.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
release:
7+
types: [published]
8+
9+
jobs:
10+
deploy:
11+
name: Deploy to Fly.io
12+
runs-on: ubuntu-latest
13+
if: github.event_name == 'push'
14+
concurrency: deploy-group
15+
environment:
16+
name: production
17+
url: https://crit.live
18+
steps:
19+
- uses: actions/checkout@v6
20+
- uses: superfly/flyctl-actions/setup-flyctl@master
21+
- run: flyctl deploy --remote-only
22+
env:
23+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
24+
25+
docker:
26+
name: Build and push Docker image
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: read
30+
packages: write
31+
steps:
32+
- uses: actions/checkout@v6
33+
34+
- name: Set up QEMU
35+
uses: docker/setup-qemu-action@v4
36+
37+
- name: Set up Docker Buildx
38+
uses: docker/setup-buildx-action@v4
39+
40+
- name: Log in to GHCR
41+
uses: docker/login-action@v4
42+
with:
43+
registry: ghcr.io
44+
username: ${{ github.actor }}
45+
password: ${{ secrets.GITHUB_TOKEN }}
46+
47+
- name: Extract metadata
48+
id: meta
49+
uses: docker/metadata-action@v6
50+
with:
51+
images: ghcr.io/${{ github.repository }}
52+
tags: |
53+
type=ref,event=branch
54+
type=semver,pattern={{version}}
55+
type=semver,pattern={{major}}.{{minor}}
56+
type=semver,pattern={{major}}
57+
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
58+
59+
- name: Build and push
60+
uses: docker/build-push-action@v7
61+
with:
62+
context: .
63+
platforms: linux/amd64,linux/arm64
64+
push: true
65+
tags: ${{ steps.meta.outputs.tags }}
66+
labels: ${{ steps.meta.outputs.labels }}
67+
cache-from: type=gha
68+
cache-to: type=gha,mode=max

assets/js/app.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,61 @@ window.addEventListener("clipboard:copy", e => {
5454
// >> liveSocket.disableLatencySim()
5555
window.liveSocket = liveSocket
5656

57+
// Home page: copy buttons
58+
document.querySelectorAll(".copy-btn").forEach(btn => {
59+
const defaultIcon = btn.querySelector(".icon-default")
60+
const copiedIcon = btn.querySelector(".icon-copied")
61+
btn.addEventListener("click", () => {
62+
const raw = btn.previousElementSibling.textContent.trim()
63+
const text = raw.replace(/^\$\s+/, "")
64+
navigator.clipboard.writeText(text).then(() => {
65+
btn.style.color = "var(--crit-green)"
66+
defaultIcon.classList.add("hidden")
67+
copiedIcon.classList.remove("hidden")
68+
setTimeout(() => {
69+
btn.style.color = ""
70+
defaultIcon.classList.remove("hidden")
71+
copiedIcon.classList.add("hidden")
72+
}, 2000)
73+
})
74+
})
75+
})
76+
77+
// Home page: feature cards scroll-triggered reveal
78+
const featuresGrid = document.getElementById("features-grid")
79+
if (featuresGrid) {
80+
const observer = new IntersectionObserver((entries) => {
81+
entries.forEach(entry => {
82+
if (entry.isIntersecting) {
83+
const cards = featuresGrid.querySelectorAll(".feature-card")
84+
cards.forEach((card, i) => {
85+
setTimeout(() => card.classList.add("revealed"), i * 80)
86+
})
87+
const selfHosting = document.querySelector("#self-hosting-card .feature-card")
88+
if (selfHosting) {
89+
setTimeout(() => selfHosting.classList.add("revealed"), cards.length * 80)
90+
}
91+
observer.unobserve(entry.target)
92+
}
93+
})
94+
}, { threshold: 0.05 })
95+
observer.observe(featuresGrid)
96+
}
97+
98+
// Home page: install tab switcher
99+
document.querySelectorAll(".install-tab").forEach(tab => {
100+
tab.addEventListener("click", () => {
101+
document.querySelectorAll(".install-tab").forEach(t => {
102+
t.classList.remove("border-(--crit-accent)", "text-(--crit-accent)")
103+
t.classList.add("border-transparent", "text-(--crit-fg-muted)")
104+
})
105+
document.querySelectorAll(".install-panel").forEach(p => p.classList.add("hidden"))
106+
tab.classList.add("border-(--crit-accent)", "text-(--crit-accent)")
107+
tab.classList.remove("border-transparent", "text-(--crit-fg-muted)")
108+
document.getElementById(tab.dataset.target).classList.remove("hidden")
109+
})
110+
})
111+
57112
// The lines below enable quality of life phoenix_live_reload
58113
// development features:
59114
//

config/runtime.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ if config_env() == :prod do
6161
System.get_env("SECRET_KEY_BASE") ||
6262
raise """
6363
environment variable SECRET_KEY_BASE is missing.
64-
You can generate one by calling: mix phx.gen.secret
64+
You can generate one by running: openssl rand -base64 64
6565
"""
6666

6767
host = System.get_env("PHX_HOST") || "example.com"

lib/crit_web/controllers/page_html/home.html.heex

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -581,14 +581,12 @@
581581
class="max-w-[1100px] mx-auto mt-2 mb-16 px-10 max-sm:px-5 w-full"
582582
id="self-hosting-card"
583583
>
584-
<div class="feature-card block border border-(--crit-border) rounded-lg bg-(--crit-bg-secondary) p-6 opacity-60">
584+
<a
585+
href="/self-hosting"
586+
class="feature-card block border border-(--crit-border) rounded-lg bg-(--crit-bg-secondary) p-6 hover:border-(--crit-accent) transition-colors"
587+
>
585588
<div class="flex items-center justify-between max-sm:flex-col max-sm:items-start max-sm:gap-4">
586589
<div>
587-
<div class="flex items-center gap-3 mb-2">
588-
<span class="font-mono text-xs tracking-widest uppercase text-(--crit-fg-muted)">
589-
Coming Soon
590-
</span>
591-
</div>
592590
<h3 class="text-lg font-semibold tracking-tight mb-1">Self-Host Crit Web</h3>
593591
<p class="text-sm text-(--crit-fg-secondary) leading-relaxed max-w-[520px]">
594592
Run your own instance with Docker. One command to start, automatic migrations, works with any Postgres database.
@@ -602,62 +600,6 @@
602600
<div class="text-(--crit-green)">✓ crit-web running on :4000</div>
603601
</div>
604602
</div>
605-
</div>
603+
</a>
606604
</section>
607605
</div>
608-
609-
<script>
610-
document.querySelectorAll(".copy-btn").forEach(btn => {
611-
const defaultIcon = btn.querySelector(".icon-default");
612-
const copiedIcon = btn.querySelector(".icon-copied");
613-
btn.addEventListener("click", () => {
614-
const raw = btn.previousElementSibling.textContent.trim();
615-
const text = raw.replace(/^\$\s+/, "");
616-
navigator.clipboard.writeText(text).then(() => {
617-
btn.style.color = "var(--crit-green)";
618-
defaultIcon.classList.add("hidden");
619-
copiedIcon.classList.remove("hidden");
620-
setTimeout(() => {
621-
btn.style.color = "";
622-
defaultIcon.classList.remove("hidden");
623-
copiedIcon.classList.add("hidden");
624-
}, 2000);
625-
});
626-
});
627-
});
628-
629-
// Feature cards scroll-triggered reveal
630-
const featuresGrid = document.getElementById("features-grid");
631-
if (featuresGrid) {
632-
const observer = new IntersectionObserver((entries) => {
633-
entries.forEach(entry => {
634-
if (entry.isIntersecting) {
635-
const cards = featuresGrid.querySelectorAll(".feature-card");
636-
cards.forEach((card, i) => {
637-
setTimeout(() => card.classList.add("revealed"), i * 80);
638-
});
639-
// Chain the self-hosting card after the last feature card
640-
const selfHosting = document.querySelector("#self-hosting-card .feature-card");
641-
if (selfHosting) {
642-
setTimeout(() => selfHosting.classList.add("revealed"), cards.length * 80);
643-
}
644-
observer.unobserve(entry.target);
645-
}
646-
});
647-
}, { threshold: 0.05 });
648-
observer.observe(featuresGrid);
649-
}
650-
651-
document.querySelectorAll(".install-tab").forEach(tab => {
652-
tab.addEventListener("click", () => {
653-
document.querySelectorAll(".install-tab").forEach(t => {
654-
t.classList.remove("border-(--crit-accent)", "text-(--crit-accent)");
655-
t.classList.add("border-transparent", "text-(--crit-fg-muted)");
656-
});
657-
document.querySelectorAll(".install-panel").forEach(p => p.classList.add("hidden"));
658-
tab.classList.add("border-(--crit-accent)", "text-(--crit-accent)");
659-
tab.classList.remove("border-transparent", "text-(--crit-fg-muted)");
660-
document.getElementById(tab.dataset.target).classList.remove("hidden");
661-
});
662-
});
663-
</script>

lib/crit_web/controllers/page_html/self_hosting.html.heex

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@
55
<h1 class="text-3xl font-bold tracking-tight mb-2">Self-Hosting</h1>
66
<p class="text-sm text-(--crit-fg-muted) mb-4">Run your own instance of Crit Web</p>
77

8-
<div class="border border-(--crit-accent) rounded-lg bg-(--crit-accent-subtle) px-5 py-4 mb-10">
9-
<p class="text-sm font-semibold text-(--crit-accent) mb-1">Coming Soon</p>
10-
<p class="text-sm text-(--crit-fg-secondary) leading-relaxed">
11-
Self-hosting support is not yet available. The documentation below is a preview of what's planned.
12-
</p>
13-
</div>
14-
158
<h2 class="text-base font-semibold mt-8 mb-2">Prerequisites</h2>
169
<ul class="text-sm leading-relaxed text-(--crit-fg-secondary) pl-5 mt-2">
1710
<li class="mb-1">Docker (with Compose)</li>
@@ -20,30 +13,24 @@
2013

2114
<h2 class="text-base font-semibold mt-8 mb-2">Quick start with Docker Compose</h2>
2215
<p class="text-sm leading-relaxed text-(--crit-fg-secondary)">
23-
Clone the repository and start the services:
16+
Download the example files and configure your environment:
2417
</p>
25-
<pre class="text-sm bg-(--crit-bg-tertiary) rounded-lg p-4 mt-3 overflow-x-auto"><code>git clone https://github.com/tomasz-tomczyk/crit-web.git
26-
cd crit-web
27-
docker compose up -d</code></pre>
18+
<pre class="text-sm bg-(--crit-bg-tertiary) rounded-lg p-4 mt-3 overflow-x-auto"><code>curl -o docker-compose.yml https://raw.githubusercontent.com/tomasz-tomczyk/crit-web/main/contrib/docker-compose.example.yml
19+
curl -o .env https://raw.githubusercontent.com/tomasz-tomczyk/crit-web/main/.env.example</code></pre>
2820
<p class="text-sm leading-relaxed text-(--crit-fg-secondary) mt-3">
29-
Before starting, generate a secret key and update <code class="text-[0.9em] bg-(--crit-bg-tertiary) px-1 py-0.5 rounded">docker-compose.yml</code>:
21+
Generate a secret key and set it in <code class="text-[0.9em] bg-(--crit-bg-tertiary) px-1 py-0.5 rounded">.env</code>:
3022
</p>
31-
<pre class="text-sm bg-(--crit-bg-tertiary) rounded-lg p-4 mt-3 overflow-x-auto"><code>docker run --rm ghcr.io/tomasz-tomczyk/crit-web:latest \
32-
/app/bin/crit eval "IO.puts(:crypto.strong_rand_bytes(64) |> Base.encode64)"</code></pre>
23+
<pre class="text-sm bg-(--crit-bg-tertiary) rounded-lg p-4 mt-3 overflow-x-auto"><code>openssl rand -base64 64</code></pre>
3324
<p class="text-sm leading-relaxed text-(--crit-fg-secondary) mt-3">
34-
Replace
35-
<code class="text-[0.9em] bg-(--crit-bg-tertiary) px-1 py-0.5 rounded">
36-
generate-a-secret-see-readme
37-
</code>
38-
in
25+
Update
3926
<code class="text-[0.9em] bg-(--crit-bg-tertiary) px-1 py-0.5 rounded">
40-
docker-compose.yml
27+
SECRET_KEY_BASE
4128
</code>
42-
with the generated value.
43-
Update
44-
<code class="text-[0.9em] bg-(--crit-bg-tertiary) px-1 py-0.5 rounded">PHX_HOST</code>
45-
if serving on a real domain.
29+
and <code class="text-[0.9em] bg-(--crit-bg-tertiary) px-1 py-0.5 rounded">PHX_HOST</code>
30+
in <code class="text-[0.9em] bg-(--crit-bg-tertiary) px-1 py-0.5 rounded">.env</code>,
31+
then start the services:
4632
</p>
33+
<pre class="text-sm bg-(--crit-bg-tertiary) rounded-lg p-4 mt-3 overflow-x-auto"><code>docker compose up -d</code></pre>
4734

4835
<h2 class="text-base font-semibold mt-8 mb-2">Environment Variables</h2>
4936
<div class="overflow-x-auto mt-3">

test/crit_web/live/dashboard_live_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
defmodule CritWeb.DashboardLiveTest do
2-
use CritWeb.ConnCase, async: true
2+
use CritWeb.ConnCase, async: false
33

44
import Phoenix.LiveViewTest
55
import Crit.ReviewsFixtures

0 commit comments

Comments
 (0)