Skip to content

Commit 52e4236

Browse files
committed
easy install script.
1 parent 991ddc5 commit 52e4236

10 files changed

Lines changed: 158 additions & 96 deletions

File tree

.github/workflows/go.yml

Lines changed: 0 additions & 22 deletions
This file was deleted.

.github/workflows/release.yml

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,32 @@
1-
name: build for all platforms
1+
name: build and release
22

33
on:
4-
release:
5-
types: [created]
4+
push:
5+
tags:
6+
- '*'
67

78
permissions:
89
contents: write
910

1011
jobs:
11-
12-
releases-matrix:
12+
build-and-release:
1313
name: Build and release binary
1414
runs-on: ubuntu-latest
15-
strategy:
16-
matrix:
17-
goos: [linux]
18-
goarch: [amd64]
19-
exclude:
20-
- goarch: arm64
21-
goos: windows
22-
steps:
23-
- uses: actions/checkout@v3
24-
- uses: wangyoucao577/go-release-action@v1.40
25-
with:
26-
github_token: ${{ secrets.GITHUB_TOKEN }}
27-
goos: ${{ matrix.goos }}
28-
goarch: ${{ matrix.goarch }}
29-
overwrite: true
30-
md5sum: false
31-
sha256sum: false
32-
compress_assets: false
33-
34-
release-docker:
35-
name: Push Docker image to Docker Hub
36-
runs-on: ubuntu-latest
3715
steps:
38-
- name: Check out the repo
39-
uses: actions/checkout@v4
40-
41-
- name: Log in to Docker Hub
42-
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-go@v4
4318
with:
44-
username: ${{ secrets.DOCKER_USERNAME }}
45-
password: ${{ secrets.DOCKER_PASSWORD }}
46-
47-
- name: Extract metadata (tags, labels) for Docker
48-
id: meta
49-
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
19+
go-version: '1.25'
20+
- name: Install musl and musl-gcc
21+
run: sudo apt-get update && sudo apt-get install -y musl-tools
22+
- name: Get templ version
23+
run: echo "TEMPL_VERSION=$(grep 'github.com/a-h/templ' go.mod | awk '{print $2}')" >> $GITHUB_ENV
24+
- name: Install templ
25+
run: go install github.com/a-h/templ/cmd/templ@${{ env.TEMPL_VERSION }}
26+
- uses: extractions/setup-just@v2
27+
- name: Build binary
28+
run: just build
29+
- name: Create release
30+
uses: softprops/action-gh-release@v2
5031
with:
51-
images: tijlxyz/khatru-pyramid
52-
53-
- name: Build and push Docker image
54-
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
55-
with:
56-
context: .
57-
file: ./Dockerfile
58-
push: true
59-
tags: ${{ steps.meta.outputs.tags }}
60-
labels: ${{ steps.meta.outputs.labels }}
61-
32+
files: pyramid-exe

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
**pyramid** serves as a wondrous furnace of communityzenship for your Nostr experience, enabling users to build and nurture vibrant communities through a hierarchical relay system. With powerful subrelay features, extensive optional configurations, and easy theming options, **pyramid** makes it effortless to create and manage personalized Nostr environments tailored to your personal or community's needs.
44

5+
## easy install
6+
7+
type this in a blank server you should rented:
8+
9+
```
10+
curl -s https://raw.githubusercontent.com/fiatjaf/pyramid/refs/heads/master/easy.sh | bash
11+
```
12+
13+
## features
14+
515
- **easy install**
616
- a single-line install setup and no need to fiddle with configuration files
717
- if you can buy VPS access you can setup one of these

curation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func processReactions(ctx context.Context, event nostr.Event) {
5353
}
5454

5555
popularThreshold := min(2, (totalMembers*global.Settings.Popular.PercentThreshold)/100)
56-
uppermostThreshold := min(2, (totalMembers*global.Settings.Uppermost.PercentThreshold)/100)
56+
uppermostThreshold := min(3, (totalMembers*global.Settings.Uppermost.PercentThreshold)/100)
5757

5858
// for all events we meet the popular threshold for
5959
for target, votes := range popularVotes {

easy.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/bin/bash
2+
3+
# ufw allow (if necessary)
4+
ufw allow http
5+
ufw allow https
6+
7+
# download binary into ./pyramid directory
8+
VERSION=$(curl -s "https://api.github.com/repos/fiatjaf/pyramid/releases/latest" | grep '"tag_name":' | cut -d '"' -f 4)
9+
mkdir -p pyramid
10+
cd pyramid
11+
rm pyramid-exe-old
12+
mv pyramid-exe pyramid-exe-old
13+
wget "https://github.com/fiatjaf/pyramid/releases/download/$VERSION/pyramid-exe"
14+
chmod +x pyramid-exe
15+
DIR=$(pwd)
16+
17+
# create systemd service file
18+
SERVICE_FILE="/etc/systemd/system/pyramid.service"
19+
sudo tee "$SERVICE_FILE" > /dev/null <<EOF
20+
[Unit]
21+
Description=pyramid relay
22+
After=network.target
23+
24+
[Service]
25+
Type=simple
26+
User=$USER
27+
ExecStart=$DIR/pyramid-exe
28+
Restart=always
29+
Environment=HOST=0.0.0.0 PORT=443
30+
31+
[Install]
32+
WantedBy=multi-user.target
33+
EOF
34+
35+
# reload systemd, enable and start
36+
sudo systemctl daemon-reload
37+
sudo systemctl enable pyramid
38+
sudo systemctl start pyramid
39+
40+
# setup motd
41+
echo '
42+
43+
### pyramid
44+
- see status: systemctl status pyramid
45+
- view logs: journalctl -xefu pyramid
46+
- restart: systemctl restart pyramid
47+
48+
' > /etc/motd
49+
50+
# print instructions
51+
IP=$(curl -s https://api.ipify.org)
52+
echo "***"
53+
echo ""
54+
echo "pyramid is running. visit http://$IP to setup."

go.mod

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
module github.com/fiatjaf/pyramid
22

3-
go 1.24.2
3+
go 1.25
44

55
require (
6-
fiatjaf.com/nostr v0.0.0-20251031160519-32bbff615a04
6+
fiatjaf.com/nostr v0.0.0-20251126120447-7261a4b515ed
77
github.com/FastFilter/xorfilter v0.2.1
88
github.com/a-h/templ v0.3.960
99
github.com/kelseyhightower/envconfig v1.4.0
@@ -60,5 +60,3 @@ require (
6060
golang.org/x/text v0.27.0 // indirect
6161
gopkg.in/yaml.v3 v3.0.1 // indirect
6262
)
63-
64-
replace fiatjaf.com/nostr => ../nostrlib

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
fiatjaf.com/nostr v0.0.0-20251126120447-7261a4b515ed h1:dip4Bxlfj4/N2oIIGCZpcYRrvfMObcXRcyBM5uYswnM=
2+
fiatjaf.com/nostr v0.0.0-20251126120447-7261a4b515ed/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
13
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
24
github.com/FastFilter/xorfilter v0.2.1/go.mod h1:aumvdkhscz6YBZF9ZA/6O4fIoNod4YR50kIVGGZ7l9I=
35
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg=

handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ func settingsHandler(w http.ResponseWriter, r *http.Request) {
107107
moderated.Relay.ServiceURL = global.Settings.WSScheme() + global.Settings.Domain + "/" + global.Settings.Moderated.HTTPBasePath
108108
popular.Relay.ServiceURL = global.Settings.WSScheme() + global.Settings.Domain + "/" + global.Settings.Popular.HTTPBasePath
109109
uppermost.Relay.ServiceURL = global.Settings.WSScheme() + global.Settings.Domain + "/" + global.Settings.Uppermost.HTTPBasePath
110+
go restartSoon()
110111
//
111112
// theme settings
112113
case "background_color":
@@ -428,6 +429,7 @@ func domainSetupHandler(w http.ResponseWriter, r *http.Request) {
428429
return
429430
}
430431

432+
go restartSoon()
431433
http.Redirect(w, r, "/", 302)
432434
return
433435
}

main.go

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -360,41 +360,78 @@ func run(ctx context.Context) error {
360360

361361
mux.Handle("/", relay)
362362

363+
g, ctx := errgroup.WithContext(ctx)
364+
365+
// copy here as we'll modify it
366+
port := global.S.Port
367+
368+
if port == "443" {
369+
// if we don't have a domain name we'll listen only on port 80 without doing the autocert dance yet
370+
if global.Settings.Domain == "" {
371+
log.Info().Msg("no domain setup yet, running only on port 80 for now")
372+
port = "80"
373+
}
374+
}
375+
363376
server := &http.Server{
364-
Addr: global.S.Host + ":" + global.S.Port,
377+
Addr: global.S.Host + ":" + port,
365378
Handler: ipBlockMiddleware(setupCheckMiddleware(mux)),
366379
BaseContext: func(_ net.Listener) context.Context {
367380
return ctx
368381
},
369382
}
370383

371-
var listenFunc func() error
372-
if global.S.Port == "443" {
384+
if port == "443" {
373385
manager := &autocert.Manager{
374386
Prompt: func(_ string) bool { return true },
375387
HostPolicy: autocert.HostWhitelist(global.Settings.Domain),
376388
Cache: autocert.DirCache("certs"),
377389
}
378-
server.TLSConfig = manager.TLSConfig()
379-
listenFunc = func() error { return server.ListenAndServeTLS("", "") }
380-
log.Info().Msg("running on https://" + global.S.Host + ":" + global.S.Port)
381-
} else {
382-
listenFunc = server.ListenAndServe
383-
log.Info().Msg("running on http://" + global.S.Host + ":" + global.S.Port)
384-
}
385390

386-
g, ctx := errgroup.WithContext(ctx)
387-
g.Go(listenFunc)
388-
g.Go(func() error {
389-
<-ctx.Done()
390-
if err := server.Shutdown(context.Background()); err != nil {
391-
return err
391+
// HTTP server on 80 for ACME challenges and user access
392+
httpServer := &http.Server{
393+
Addr: global.S.Host + ":80",
394+
Handler: manager.HTTPHandler(mux),
395+
BaseContext: func(_ net.Listener) context.Context {
396+
return ctx
397+
},
392398
}
393-
if err := server.Close(); err != nil {
394-
return err
399+
g.Go(func() error { return httpServer.ListenAndServe() })
400+
401+
// HTTPS server on 443
402+
httpsServer := &http.Server{
403+
Addr: global.S.Host + ":443",
404+
Handler: ipBlockMiddleware(mux),
405+
BaseContext: func(_ net.Listener) context.Context {
406+
return ctx
407+
},
395408
}
396-
return nil
397-
})
409+
httpsServer.TLSConfig = manager.TLSConfig()
410+
411+
g.Go(func() error { return httpsServer.ListenAndServeTLS("", "") })
412+
log.Info().Msg("running on https://" + global.S.Host + ":443 and http://" + global.S.Host + ":80")
413+
g.Go(func() error {
414+
<-ctx.Done()
415+
httpsServer.Shutdown(context.Background())
416+
httpServer.Shutdown(context.Background())
417+
return nil
418+
})
419+
} else {
420+
g.Go(server.ListenAndServe)
421+
log.Info().Msg("running on http://" + global.S.Host + ":" + port)
422+
423+
g.Go(func() error {
424+
<-ctx.Done()
425+
if err := server.Shutdown(context.Background()); err != nil {
426+
return err
427+
}
428+
if err := server.Close(); err != nil {
429+
return err
430+
}
431+
return nil
432+
})
433+
}
434+
398435
return g.Wait()
399436
}
400437

setup.templ

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,18 @@ templ domainSetupPage() {
4545
set domain
4646
</button>
4747
</form>
48+
<div id="dns-message" class="mt-4 text-sm text-stone-500 dark:text-stone-400 hidden"></div>
4849
</div>
4950
</div>
51+
<script>
52+
const hostname = window.location.hostname;
53+
const isIP = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname);
54+
if (window.location.protocol === 'http:' && isIP && hostname !== '0.0.0.0' && hostname != '127.0.0.1') {
55+
const messageDiv = document.getElementById('dns-message');
56+
messageDiv.innerHTML = `in your domain name provider, set a DNS "<code>A</code>" record pointing to "<code>${hostname}</code>".`;
57+
messageDiv.classList.remove('hidden');
58+
}
59+
</script>
5060
</body>
5161
</html>
5262
}

0 commit comments

Comments
 (0)