snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
1
misc/README.md
vendored
Normal file
1
misc/README.md
vendored
Normal file
@@ -0,0 +1 @@
|
||||
This folder contains various tools, deploy scripts etc for Centrifugo.
|
||||
78
misc/benchmarking/k6/benchmark.js
Normal file
78
misc/benchmarking/k6/benchmark.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import ws from 'k6/ws';
|
||||
import { check } from 'k6';
|
||||
import crypto from "k6/crypto";
|
||||
import encoding from "k6/encoding";
|
||||
|
||||
const hmacSecret = "secret";
|
||||
|
||||
const algToHash = {
|
||||
HS256: "sha256",
|
||||
HS384: "sha384",
|
||||
HS512: "sha512"
|
||||
};
|
||||
|
||||
function sign(data, hashAlg, secret) {
|
||||
let hasher = crypto.createHMAC(hashAlg, secret);
|
||||
hasher.update(data);
|
||||
// Some manual base64 encoding as `Hasher.digest(encodingType)` doesn't support that encoding type yet.
|
||||
return hasher.digest("base64").replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "");
|
||||
}
|
||||
|
||||
function encode(payload, secret, algorithm) {
|
||||
algorithm = algorithm || "HS256";
|
||||
let header = encoding.b64encode(JSON.stringify({ typ: "JWT", alg: algorithm }), "rawurl");
|
||||
payload = encoding.b64encode(JSON.stringify(payload), "rawurl");
|
||||
let sig = sign(header + "." + payload, algToHash[algorithm], secret);
|
||||
return [header, payload, sig].join(".");
|
||||
}
|
||||
|
||||
// Generate a token for each user separately using HMAC JWT
|
||||
function generateToken(userId) {
|
||||
const payload = {
|
||||
sub: userId,
|
||||
exp: Math.floor(Date.now() / 1000) + 60, // Expires in 60 seconds
|
||||
};
|
||||
return encode(payload, hmacSecret, "HS256")
|
||||
}
|
||||
|
||||
export let options = {
|
||||
stages: [
|
||||
{ duration: "10s", target: 1000 },
|
||||
{ duration: "50s", target: 1000 },
|
||||
]
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const url = 'ws://localhost:8000/connection/websocket';
|
||||
|
||||
const user = `user_${__VU}_${__ITER}`;
|
||||
const token = generateToken(user);
|
||||
|
||||
const response = ws.connect(url, {}, function (socket) {
|
||||
socket.on('open', () => {
|
||||
const connectCommand = JSON.stringify({ id: 1, connect: { token: token } });
|
||||
const subscribeCommand = JSON.stringify({ id: 2, subscribe: { channel: 'test' } });
|
||||
socket.send(connectCommand + "\n" + subscribeCommand);
|
||||
});
|
||||
|
||||
socket.on('message', (message) => {
|
||||
// Respond to server pings.
|
||||
const substrings = message.split("\n");
|
||||
if (substrings.includes("{}")) {
|
||||
socket.send('{}');
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('close', () => {});
|
||||
|
||||
socket.on('error', (error) => {
|
||||
console.log('WebSocket error: ', error);
|
||||
});
|
||||
|
||||
socket.setTimeout(() => {
|
||||
socket.close();
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
check(response, { "status is 101": (r) => r && r.status === 101 });
|
||||
}
|
||||
27
misc/benchmarking/k6/readme.md
Normal file
27
misc/benchmarking/k6/readme.md
Normal file
@@ -0,0 +1,27 @@
|
||||
A simple script which demonstrates benchmarking Centrifugo with [k6](https://k6.io/). You can use it as a starting point.
|
||||
|
||||
It connects to Centrifugo with JWT per user, subscribes to the `test` channel, waits some time and exits.
|
||||
|
||||
## Running test
|
||||
|
||||
Run Centrifugo with config like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"token_hmac_secret_key": "secret",
|
||||
"admin": true,
|
||||
"admin_password": "password",
|
||||
"admin_secret": "0453661f-8b95-4f75-ab49-cfb2e3b2024f",
|
||||
"api_key": "7d8cee29-f5c0-419e-ae70-760767b21f8e",
|
||||
"allow_subscribe_for_client": true,
|
||||
"allowed_origins": []
|
||||
}
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
k6 run benchmark.js
|
||||
```
|
||||
|
||||
Make sure [your system is tuned](https://centrifugal.dev/docs/server/infra_tuning) for dealing with many connections.
|
||||
48
misc/benchmarking/k6/uni_ws/benchmark.js
Normal file
48
misc/benchmarking/k6/uni_ws/benchmark.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import ws from 'k6/ws';
|
||||
import { check } from 'k6';
|
||||
|
||||
export let options = {
|
||||
stages: [
|
||||
// Ramp up to 5000 connections over 60 seconds
|
||||
{ duration: '60s', target: 5000 },
|
||||
// Hold 5000 connections for 4 minutes
|
||||
{ duration: '4m', target: 5000 },
|
||||
// Ramp down to 0 connections
|
||||
{ duration: '10s', target: 0 },
|
||||
],
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const url = 'ws://localhost:8000/connection/uni_websocket?cf_connect={}';
|
||||
|
||||
const response = ws.connect(url, {}, function (socket) {
|
||||
socket.on('open', function open() {
|
||||
console.log('WebSocket connection established');
|
||||
});
|
||||
|
||||
socket.on('message', function message(data) {
|
||||
console.log('Received message:', data);
|
||||
});
|
||||
|
||||
socket.on('close', function close() {
|
||||
console.log('WebSocket connection closed');
|
||||
});
|
||||
|
||||
socket.on('error', function error(e) {
|
||||
console.log('WebSocket error:', e.error());
|
||||
});
|
||||
|
||||
// Keep the connection alive for the duration of the test
|
||||
// The connection will be closed when the VU iteration ends
|
||||
socket.setTimeout(function () {
|
||||
socket.close();
|
||||
}, 300000); // 5 minutes timeout as safety measure
|
||||
|
||||
// Send a ping every 30 seconds to keep connection active
|
||||
socket.setInterval(function () {
|
||||
socket.ping();
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
check(response, { 'status is 101': (r) => r && r.status === 101 });
|
||||
}
|
||||
5
misc/benchmarking/k6/uni_ws/readme.md
Normal file
5
misc/benchmarking/k6/uni_ws/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Makes 5k conns to unidirectional websocket endpoint during 1 min, waits 4 mins, stops.
|
||||
|
||||
```
|
||||
k6 run benchmark.js
|
||||
```
|
||||
1
misc/benchmarking/readme.md
Normal file
1
misc/benchmarking/readme.md
Normal file
@@ -0,0 +1 @@
|
||||
Various helpers for benchmarking Centrifugo.
|
||||
11
misc/clickhouse_cluster/.gitignore
vendored
Normal file
11
misc/clickhouse_cluster/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.vscode
|
||||
.idea
|
||||
zookeper/
|
||||
clickhouse01/
|
||||
clickhouse01_db/
|
||||
clickhouse02/
|
||||
clickhouse02_db/
|
||||
clickhouse03/
|
||||
clickhouse03_db/
|
||||
clickhouse04/
|
||||
clickhouse04_db/
|
||||
28
misc/clickhouse_cluster/Makefile
Normal file
28
misc/clickhouse_cluster/Makefile
Normal file
@@ -0,0 +1,28 @@
|
||||
.PHONY: config
|
||||
config:
|
||||
rm -rf clickhouse01 clickhouse02 clickhouse03 clickhouse04
|
||||
mkdir -p clickhouse01 clickhouse02 clickhouse03 clickhouse04
|
||||
rm -rf clickhouse01_db clickhouse02_db clickhouse03_db clickhouse04_db
|
||||
mkdir -p clickhouse01_db clickhouse02_db clickhouse03_db clickhouse04_db
|
||||
rm -rf zookeeper
|
||||
mkdir -p zookeeper
|
||||
REPLICA=01 SHARD=01 envsubst < config.xml > clickhouse01/config.xml
|
||||
REPLICA=02 SHARD=01 envsubst < config.xml > clickhouse02/config.xml
|
||||
REPLICA=03 SHARD=02 envsubst < config.xml > clickhouse03/config.xml
|
||||
REPLICA=04 SHARD=02 envsubst < config.xml > clickhouse04/config.xml
|
||||
cp users.xml clickhouse01/users.xml
|
||||
cp users.xml clickhouse02/users.xml
|
||||
cp users.xml clickhouse03/users.xml
|
||||
cp users.xml clickhouse04/users.xml
|
||||
|
||||
.PHONY: up
|
||||
up:
|
||||
docker-compose up -d
|
||||
|
||||
.PHONY: start
|
||||
start:
|
||||
docker-compose start
|
||||
|
||||
.PHONY: down
|
||||
down:
|
||||
docker-compose down
|
||||
82
misc/clickhouse_cluster/config.xml
Normal file
82
misc/clickhouse_cluster/config.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0"?>
|
||||
<company>
|
||||
<logger>
|
||||
<level>debug</level>
|
||||
<console>true</console>
|
||||
<log remove="remove"/>
|
||||
<errorlog remove="remove"/>
|
||||
</logger>
|
||||
|
||||
<query_log>
|
||||
<database>system</database>
|
||||
<table>query_log</table>
|
||||
</query_log>
|
||||
|
||||
<listen_host>0.0.0.0</listen_host>
|
||||
<http_port>8123</http_port>
|
||||
<tcp_port>9000</tcp_port>
|
||||
<interserver_http_host>clickhouse${REPLICA}</interserver_http_host>
|
||||
<interserver_http_port>9009</interserver_http_port>
|
||||
|
||||
<max_connections>4096</max_connections>
|
||||
<keep_alive_timeout>3</keep_alive_timeout>
|
||||
<max_concurrent_queries>100</max_concurrent_queries>
|
||||
<uncompressed_cache_size>8589934592</uncompressed_cache_size>
|
||||
<mark_cache_size>5368709120</mark_cache_size>
|
||||
|
||||
<path>/var/lib/clickhouse/</path>
|
||||
<tmp_path>/var/lib/clickhouse/tmp/</tmp_path>
|
||||
<user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
|
||||
|
||||
<users_config>users.xml</users_config>
|
||||
<default_profile>default</default_profile>
|
||||
<default_database>default</default_database>
|
||||
<timezone>Europe/Moscow</timezone>
|
||||
<mlock_executable>false</mlock_executable>
|
||||
|
||||
<remote_servers>
|
||||
<centrifugo_cluster>
|
||||
<shard>
|
||||
<internal_replication>true</internal_replication>
|
||||
<replica>
|
||||
<host>clickhouse01</host>
|
||||
<port>9000</port>
|
||||
</replica>
|
||||
<replica>
|
||||
<host>clickhouse02</host>
|
||||
<port>9000</port>
|
||||
</replica>
|
||||
</shard>
|
||||
<shard>
|
||||
<internal_replication>true</internal_replication>
|
||||
<replica>
|
||||
<host>clickhouse03</host>
|
||||
<port>9000</port>
|
||||
</replica>
|
||||
<replica>
|
||||
<host>clickhouse04</host>
|
||||
<port>9000</port>
|
||||
</replica>
|
||||
</shard>
|
||||
</centrifugo_cluster>
|
||||
</remote_servers>
|
||||
|
||||
<zookeeper>
|
||||
<node index="1">
|
||||
<host>zookeeper</host>
|
||||
<port>2181</port>
|
||||
</node>
|
||||
</zookeeper>
|
||||
|
||||
<macros>
|
||||
<cluster>centrifugo_cluster</cluster>
|
||||
<shard>${SHARD}</shard>
|
||||
<replica>clickhouse${REPLICA}</replica>
|
||||
</macros>
|
||||
|
||||
<distributed_ddl>
|
||||
<path>/clickhouse/task_queue/ddl</path>
|
||||
</distributed_ddl>
|
||||
|
||||
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
|
||||
</company>
|
||||
68
misc/clickhouse_cluster/docker-compose.yml
Normal file
68
misc/clickhouse_cluster/docker-compose.yml
Normal file
@@ -0,0 +1,68 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
zookeeper:
|
||||
image: zookeeper:latest
|
||||
container_name: zookeeper
|
||||
hostname: zookeeper
|
||||
environment:
|
||||
ALLOW_ANONYMOUS_LOGIN: "yes"
|
||||
healthcheck:
|
||||
test: echo "ruok" | nc -w 2 localhost 2181
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
volumes:
|
||||
- ${PWD}/zookeeper:/data
|
||||
- ${PWD}/zookeeper:/logs
|
||||
clickhouse01:
|
||||
image: clickhouse/clickhouse-server:23-alpine
|
||||
container_name: clickhouse01
|
||||
hostname: clickhouse01
|
||||
ports:
|
||||
- "127.0.0.1:8123:8123"
|
||||
- "127.0.0.1:9000:9000"
|
||||
volumes:
|
||||
- ${PWD}/clickhouse01:/etc/clickhouse-server
|
||||
- ${PWD}/clickhouse01_db:/var/lib/clickhouse
|
||||
depends_on:
|
||||
zookeeper:
|
||||
condition: service_healthy
|
||||
clickhouse02:
|
||||
image: clickhouse/clickhouse-server:23-alpine
|
||||
container_name: clickhouse02
|
||||
hostname: clickhouse02
|
||||
ports:
|
||||
- "127.0.0.1:8124:8123"
|
||||
- "127.0.0.1:9001:9000"
|
||||
volumes:
|
||||
- ${PWD}/clickhouse02:/etc/clickhouse-server
|
||||
- ${PWD}/clickhouse02_db:/var/lib/clickhouse
|
||||
depends_on:
|
||||
zookeeper:
|
||||
condition: service_healthy
|
||||
clickhouse03:
|
||||
image: clickhouse/clickhouse-server:23-alpine
|
||||
container_name: clickhouse03
|
||||
hostname: clickhouse03
|
||||
ports:
|
||||
- "127.0.0.1:8125:8123"
|
||||
- "127.0.0.1:9002:9000"
|
||||
volumes:
|
||||
- ${PWD}/clickhouse03:/etc/clickhouse-server
|
||||
- ${PWD}/clickhouse03_db:/var/lib/clickhouse
|
||||
depends_on:
|
||||
zookeeper:
|
||||
condition: service_healthy
|
||||
clickhouse04:
|
||||
image: clickhouse/clickhouse-server:23-alpine
|
||||
container_name: clickhouse04
|
||||
hostname: clickhouse04
|
||||
ports:
|
||||
- "127.0.0.1:8126:8123"
|
||||
- "127.0.0.1:9003:9000"
|
||||
volumes:
|
||||
- ${PWD}/clickhouse04:/etc/clickhouse-server
|
||||
- ${PWD}/clickhouse04_db:/var/lib/clickhouse
|
||||
depends_on:
|
||||
zookeeper:
|
||||
condition: service_healthy
|
||||
15
misc/clickhouse_cluster/readme.md
Normal file
15
misc/clickhouse_cluster/readme.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Clickhouse Cluster
|
||||
|
||||
Clickhouse cluster with 2 shards and 2 replicas built with docker-compose.
|
||||
|
||||
## Run
|
||||
|
||||
```
|
||||
make config
|
||||
docker compose up
|
||||
```
|
||||
|
||||
## Profiles
|
||||
|
||||
- `default` - no password
|
||||
- `admin` - password `123`
|
||||
43
misc/clickhouse_cluster/users.xml
Normal file
43
misc/clickhouse_cluster/users.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0"?>
|
||||
<company>
|
||||
<profiles>
|
||||
<default>
|
||||
<max_memory_usage>10000000000</max_memory_usage>
|
||||
<use_uncompressed_cache>0</use_uncompressed_cache>
|
||||
<load_balancing>in_order</load_balancing>
|
||||
<log_queries>1</log_queries>
|
||||
</default>
|
||||
</profiles>
|
||||
|
||||
<users>
|
||||
<default>
|
||||
<password></password>
|
||||
<profile>default</profile>
|
||||
<networks>
|
||||
<ip>::/0</ip>
|
||||
</networks>
|
||||
<quota>default</quota>
|
||||
</default>
|
||||
<admin>
|
||||
<password>123</password>
|
||||
<profile>default</profile>
|
||||
<networks>
|
||||
<ip>::/0</ip>
|
||||
</networks>
|
||||
<quota>default</quota>
|
||||
</admin>
|
||||
</users>
|
||||
|
||||
<quotas>
|
||||
<default>
|
||||
<interval>
|
||||
<duration>3600</duration>
|
||||
<queries>0</queries>
|
||||
<errors>0</errors>
|
||||
<result_rows>0</result_rows>
|
||||
<read_rows>0</read_rows>
|
||||
<execution_time>0</execution_time>
|
||||
</interval>
|
||||
</default>
|
||||
</quotas>
|
||||
</company>
|
||||
113
misc/kafka_producer/main.go
Normal file
113
misc/kafka_producer/main.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// kafka_producer produces publish commands to a Kafka topic for Centrifugo to consume.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./misc/kafka_producer/ -broker localhost:29092 -topic centrifugo -channel test -interval 1s
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/twmb/franz-go/pkg/kadm"
|
||||
"github.com/twmb/franz-go/pkg/kgo"
|
||||
)
|
||||
|
||||
type publishRequest struct {
|
||||
Channel string `json:"channel"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
type methodPayload struct {
|
||||
Method string `json:"method"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
broker := flag.String("broker", "localhost:29092", "Kafka broker address")
|
||||
topic := flag.String("topic", "centrifugo", "Kafka topic to produce to")
|
||||
channel := flag.String("channel", "test", "Centrifugo channel to publish to")
|
||||
interval := flag.Duration("interval", time.Second, "Interval between messages")
|
||||
count := flag.Int("count", 0, "Number of messages to send (0 = unlimited)")
|
||||
flag.Parse()
|
||||
|
||||
client, err := kgo.NewClient(kgo.SeedBrokers(*broker))
|
||||
if err != nil {
|
||||
log.Fatal("error creating Kafka client:", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
// Ensure topic exists (use a separate client since kadm.Close closes the underlying kgo client).
|
||||
admClient, err := kgo.NewClient(kgo.SeedBrokers(*broker))
|
||||
if err != nil {
|
||||
log.Fatal("error creating admin client:", err)
|
||||
}
|
||||
adm := kadm.NewClient(admClient)
|
||||
resp, err := adm.CreateTopics(ctx, 1, 1, nil, *topic)
|
||||
if err != nil {
|
||||
log.Fatal("error creating topic:", err)
|
||||
}
|
||||
for _, t := range resp.Sorted() {
|
||||
if t.Err != nil && !strings.Contains(t.Err.Error(), "TOPIC_ALREADY_EXISTS") {
|
||||
log.Fatalf("error creating topic %s: %v", t.Topic, t.Err)
|
||||
}
|
||||
log.Printf("topic %s ready", t.Topic)
|
||||
}
|
||||
adm.Close()
|
||||
|
||||
i := 0
|
||||
for {
|
||||
i++
|
||||
if *count > 0 && i > *count {
|
||||
break
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"message": fmt.Sprintf("hello #%d", i),
|
||||
"ts": time.Now().UnixMilli(),
|
||||
})
|
||||
|
||||
publishReq, _ := json.Marshal(publishRequest{
|
||||
Channel: *channel,
|
||||
Data: data,
|
||||
})
|
||||
|
||||
msg, _ := json.Marshal(methodPayload{
|
||||
Method: "publish",
|
||||
Payload: publishReq,
|
||||
})
|
||||
|
||||
record := &kgo.Record{
|
||||
Topic: *topic,
|
||||
Value: msg,
|
||||
}
|
||||
|
||||
res := client.ProduceSync(ctx, record)
|
||||
err := res.FirstErr()
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
break
|
||||
}
|
||||
log.Println("error producing:", err)
|
||||
} else {
|
||||
log.Printf("produced message #%d to topic=%s channel=%s %s (offset %d)", i, *topic, *channel, string(data), res[0].Record.Offset)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(*interval):
|
||||
}
|
||||
}
|
||||
}
|
||||
32
misc/kafka_producer/readme.md
Normal file
32
misc/kafka_producer/readme.md
Normal file
@@ -0,0 +1,32 @@
|
||||
Produce messages to Kafka topic to quickly test out Centrifugo Kafka consumer.
|
||||
|
||||
Run Centrifugo with:
|
||||
|
||||
```json
|
||||
{
|
||||
"consumers": [
|
||||
{
|
||||
"name": "kafka_test",
|
||||
"enabled": true,
|
||||
"type": "kafka",
|
||||
"kafka": {
|
||||
"brokers": ["localhost:29092"],
|
||||
"topics": ["centrifugo"],
|
||||
"consumer_group": "centrifugo"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Use `docker-compose.yml` of Centrifugo:
|
||||
|
||||
```
|
||||
docker compose up kafka
|
||||
```
|
||||
|
||||
And:
|
||||
|
||||
```
|
||||
go run ./misc/kafka_producer/
|
||||
```
|
||||
70
misc/nats_cluster/create_cluster.sh
Normal file
70
misc/nats_cluster/create_cluster.sh
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to check if a NATS node is up
|
||||
wait_for_nats() {
|
||||
local port=$1
|
||||
while ! nc -z localhost "$port" > /dev/null 2>&1; do
|
||||
echo "Waiting for NATS node on port $port to start..."
|
||||
sleep 1
|
||||
done
|
||||
echo "NATS node on port $port is available."
|
||||
}
|
||||
|
||||
# Array to hold PIDs of NATS server processes
|
||||
declare -a nats_pids
|
||||
|
||||
# Function to start NATS cluster nodes
|
||||
start_nats_cluster() {
|
||||
local nodes=$1
|
||||
local base_port=4222
|
||||
local cluster_port_base=5222
|
||||
local log_dir="./nats_logs"
|
||||
local cluster_nodes=()
|
||||
|
||||
# Create log directory, clean previous
|
||||
rm -rf "$log_dir"
|
||||
mkdir -p "$log_dir"
|
||||
|
||||
# Start NATS nodes
|
||||
for ((i=0; i<nodes; i++)); do
|
||||
local port=$((base_port + i))
|
||||
local cluster_port=$((cluster_port_base + i))
|
||||
local log_file="$log_dir/nats_node_${port}.log"
|
||||
|
||||
# Start a NATS server
|
||||
nats-server --port "$port" --config server.conf --cluster nats://localhost:"$cluster_port" \
|
||||
--routes nats://localhost:"$cluster_port_base" \
|
||||
> "$log_file" 2>&1 &
|
||||
nats_pids[i]=$!
|
||||
cluster_nodes+=("localhost:$port")
|
||||
done
|
||||
|
||||
# Wait for all nodes to become available
|
||||
for port in "${cluster_nodes[@]}"; do
|
||||
wait_for_nats "${port##*:}"
|
||||
done
|
||||
}
|
||||
|
||||
# Function to shut down NATS cluster nodes
|
||||
shutdown_nats_cluster() {
|
||||
echo "Shutting down NATS cluster nodes..."
|
||||
for pid in "${nats_pids[@]}"; do
|
||||
kill "$pid"
|
||||
done
|
||||
}
|
||||
|
||||
# Trap Ctrl+C (SIGINT) to call shutdown function
|
||||
trap shutdown_nats_cluster SIGINT
|
||||
|
||||
# Check if the number of nodes is provided
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <number_of_nodes>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start the NATS cluster
|
||||
start_nats_cluster "$1"
|
||||
|
||||
# Wait until a keyboard interrupt (Ctrl+C) is detected
|
||||
echo "NATS cluster is running. Press Ctrl+C to stop."
|
||||
wait
|
||||
7
misc/nats_cluster/server.conf
Normal file
7
misc/nats_cluster/server.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
accounts: {
|
||||
$SYS: {
|
||||
users: [
|
||||
{user: admin, password: password}
|
||||
]
|
||||
}
|
||||
}
|
||||
24
misc/packaging/centrifugo.service
Normal file
24
misc/packaging/centrifugo.service
Normal file
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=Centrifugo real-time messaging server
|
||||
Documentation=https://github.com/centrifugal/centrifugo
|
||||
# start once the network and logging subsystems available
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=centrifugo
|
||||
Group=centrifugo
|
||||
LimitNOFILE=65536
|
||||
Environment="CONFIG=/etc/centrifugo/config.json"
|
||||
EnvironmentFile=/etc/default/centrifugo
|
||||
ExecStartPre=/usr/bin/centrifugo checkconfig -c $CONFIG
|
||||
ExecStart=/usr/bin/centrifugo -c $CONFIG $CENTRIFUGO_OPTS
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
ExecStop=/bin/kill -SIGTERM $MAINPID
|
||||
TimeoutStopSec=10
|
||||
KillMode=control-group
|
||||
RestartSec=1
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Alias=centrifugo.service
|
||||
30
misc/packaging/centrifugo.upstart
Normal file
30
misc/packaging/centrifugo.upstart
Normal file
@@ -0,0 +1,30 @@
|
||||
# Upstart configuration file for centrifugo
|
||||
|
||||
description "Centrifugo real-time messaging server"
|
||||
|
||||
author "Alexandr Emelin <frvzmb@gmail.com>"
|
||||
|
||||
start on runlevel [2345]
|
||||
|
||||
# Always restart the runner if it dies / exits
|
||||
respawn
|
||||
|
||||
# Log to /var/log/upstart/
|
||||
console log
|
||||
|
||||
# Run the process as centrifugo:centrifugo
|
||||
setuid centrifugo
|
||||
setgid centrifugo
|
||||
|
||||
limit nofile 65536 65536
|
||||
|
||||
script
|
||||
CONFIG=/etc/centrifugo/config.json
|
||||
CENTRIFUGO_OPTS=
|
||||
|
||||
if [ -f /etc/default/centrifugo ]; then
|
||||
. /etc/default/centrifugo
|
||||
fi
|
||||
|
||||
exec /usr/bin/centrifugo -c $CONFIG $CENTRIFUGO_OPTS
|
||||
end script
|
||||
159
misc/packaging/initd.el6.sh
Normal file
159
misc/packaging/initd.el6.sh
Normal file
@@ -0,0 +1,159 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# /etc/init.d/centrifugo
|
||||
#
|
||||
# Startup script for centrifugo
|
||||
#
|
||||
# chkconfig: 2345 20 80
|
||||
# description: Starts and stops centrifugo
|
||||
|
||||
# Source function library.
|
||||
if [ -f /etc/init.d/functions ] ; then
|
||||
. /etc/init.d/functions
|
||||
elif [ -f /etc/rc.d/init.d/functions ] ; then
|
||||
. /etc/rc.d/init.d/functions
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Daemon options
|
||||
CENTRIFUGO_OPTS=
|
||||
|
||||
# Process name
|
||||
NAME=centrifugo
|
||||
|
||||
USER=centrifugo
|
||||
GROUP=centrifugo
|
||||
|
||||
# Daemon name, where is the actual executable
|
||||
# If the daemon is not there, then exit.
|
||||
DAEMON=/usr/bin/centrifugo
|
||||
|
||||
# Configuration file
|
||||
CONFIG=/etc/centrifugo/config.json
|
||||
|
||||
# Max open files
|
||||
OPEN_FILE_LIMIT=65536
|
||||
|
||||
# Logging
|
||||
if [ -z "$STDOUT" ]; then
|
||||
STDOUT=/var/log/centrifugo/centrifugo.log
|
||||
fi
|
||||
|
||||
if [ ! -f "$STDOUT" ]; then
|
||||
mkdir -p $(dirname $STDOUT)
|
||||
fi
|
||||
|
||||
if [ -z "$STDERR" ]; then
|
||||
STDERR=/var/log/centrifugo/centrifugo.log
|
||||
fi
|
||||
|
||||
if [ ! -f "$STDERR" ]; then
|
||||
mkdir -p $(dirname $STDERR)
|
||||
fi
|
||||
|
||||
lockfile="/var/lock/subsys/centrifugo"
|
||||
pidfile="/var/run/centrifugo.pid"
|
||||
|
||||
[ -e /etc/sysconfig/centrifugo ] && . /etc/sysconfig/centrifugo
|
||||
|
||||
if ! [ -x $DAEMON ]; then
|
||||
echo "$DAEMON binary not found."
|
||||
exit 5
|
||||
fi
|
||||
|
||||
start() {
|
||||
if ! [ -f $CONFIG ]; then
|
||||
echo "configuration file not found $CONFIG"
|
||||
exit 6
|
||||
fi
|
||||
|
||||
# Bump the file limits, before launching the daemon.
|
||||
ulimit -n $OPEN_FILE_LIMIT
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
configtest || return $?
|
||||
echo -n $"Starting $NAME: "
|
||||
daemon --pidfile=$pidfile --user centrifugo --check=$DAEMON "nohup $DAEMON -c $CONFIG $CENTRIFUGO_OPTS >>$STDOUT 2>&1 &"
|
||||
retval=$?
|
||||
echo
|
||||
[ $retval -eq 0 ] && touch $lockfile
|
||||
pgrep -f "$DAEMON -c $CONFIG" > $pidfile
|
||||
return $retval
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping $NAME: "
|
||||
killproc -p $pidfile $NAME -TERM
|
||||
retval=$?
|
||||
if [ $retval -eq 0 ]; then
|
||||
if [ "$CONSOLETYPE" != "serial" ]; then
|
||||
echo -en "\\033[16G"
|
||||
fi
|
||||
while rh_status_q
|
||||
do
|
||||
sleep 1
|
||||
echo -n $"."
|
||||
done
|
||||
rm -f $lockfile
|
||||
fi
|
||||
echo
|
||||
return $retval
|
||||
}
|
||||
|
||||
restart() {
|
||||
configtest || return $?
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
reload() {
|
||||
configtest || return $?
|
||||
echo -n $"Reloading $NAME: "
|
||||
killproc $DAEMON -HUP
|
||||
sleep 1
|
||||
RETVAL=$?
|
||||
echo
|
||||
}
|
||||
|
||||
configtest() {
|
||||
$DAEMON checkconfig -c $CONFIG
|
||||
}
|
||||
|
||||
rh_status() {
|
||||
status -p $pidfile $NAME
|
||||
}
|
||||
|
||||
rh_status_q() {
|
||||
rh_status >/dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
rh_status_q && exit 0
|
||||
$1
|
||||
;;
|
||||
stop)
|
||||
rh_status_q || exit 0
|
||||
$1
|
||||
;;
|
||||
restart|configtest)
|
||||
$1
|
||||
;;
|
||||
reload)
|
||||
rh_status_q || exit 7
|
||||
$1
|
||||
;;
|
||||
status|status_q)
|
||||
rh_$1
|
||||
;;
|
||||
condrestart)
|
||||
rh_status_q && restart || exit 0
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 {start|stop|reload|status|restart|configtest|condrestart}"
|
||||
exit 2
|
||||
esac
|
||||
262
misc/packaging/initd.sh
Normal file
262
misc/packaging/initd.sh
Normal file
@@ -0,0 +1,262 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: centrifugo
|
||||
# Required-Start: $all
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start centrifugo at boot time
|
||||
### END INIT INFO
|
||||
|
||||
# Command-line options that can be set in /etc/default/centrifugo. These will override
|
||||
# any config file values. Example: CENTRIFUGO_OPTS="--engine=redis"
|
||||
DEFAULT=/etc/default/centrifugo
|
||||
|
||||
# Daemon options
|
||||
CENTRIFUGO_OPTS=
|
||||
|
||||
# Process name
|
||||
readonly NAME=centrifugo
|
||||
|
||||
# Absolute path to the printf executable.
|
||||
readonly PRINT=/usr/bin/printf
|
||||
|
||||
# Absolute path to the which executable.
|
||||
readonly WHICH=/usr/bin/which
|
||||
|
||||
# User and group
|
||||
USER=centrifugo
|
||||
GROUP=centrifugo
|
||||
|
||||
# Daemon name, where is the actual executable
|
||||
# If the daemon is not there, then exit.
|
||||
DAEMON=/usr/bin/centrifugo
|
||||
[ -x $DAEMON ] || exit 5
|
||||
|
||||
# Configuration file
|
||||
CONFIG=/etc/centrifugo/config.json
|
||||
|
||||
# PID file for the daemon
|
||||
PIDFILE=/var/run/centrifugo/centrifugo.pid
|
||||
PIDDIR=`dirname $PIDFILE`
|
||||
if [ ! -d "$PIDDIR" ]; then
|
||||
mkdir -p $PIDDIR
|
||||
chown $USER:$GROUP $PIDDIR
|
||||
fi
|
||||
|
||||
# Max open files
|
||||
OPEN_FILE_LIMIT=65536
|
||||
|
||||
if [ -r /lib/lsb/init-functions ]; then
|
||||
source /lib/lsb/init-functions
|
||||
fi
|
||||
|
||||
readonly EC_INVALID_ARGUMENT=2
|
||||
readonly EC_SUPER_USER_ONLY=4
|
||||
readonly EC_DAEMON_NOT_FOUND=5
|
||||
readonly EC_RELOADING_FAILED=95
|
||||
readonly EC_RESTART_STOP_FAILED=96
|
||||
readonly EC_RESTART_START_FAILED=97
|
||||
readonly EC_START_FAILED=98
|
||||
readonly EC_STOP_FAILED=99
|
||||
|
||||
# Logging
|
||||
if [ -z "$STDOUT" ]; then
|
||||
STDOUT=/var/log/centrifugo/centrifugo.log
|
||||
fi
|
||||
|
||||
if [ ! -f "$STDOUT" ]; then
|
||||
mkdir -p $(dirname $STDOUT)
|
||||
fi
|
||||
|
||||
if [ -z "$STDERR" ]; then
|
||||
STDERR=/var/log/centrifugo/centrifugo.log
|
||||
fi
|
||||
|
||||
if [ ! -f "$STDERR" ]; then
|
||||
mkdir -p $(dirname $STDERR)
|
||||
fi
|
||||
|
||||
# Overwrite init script variables with /etc/default/centrifugo values
|
||||
if [ -r $DEFAULT ]; then
|
||||
source $DEFAULT
|
||||
fi
|
||||
|
||||
# Exit the script with an error code.
|
||||
die() {
|
||||
log_end_msg $1
|
||||
exit $1
|
||||
}
|
||||
|
||||
# End the script.
|
||||
end() {
|
||||
log_end_msg 0
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Display failure message.
|
||||
fail() {
|
||||
log_failure_msg "${NAME}" "$1"
|
||||
}
|
||||
|
||||
# Display success message.
|
||||
ok() {
|
||||
log_success_msg "${NAME}" "$1"
|
||||
}
|
||||
|
||||
# Reloads the service.
|
||||
reload_service()
|
||||
{
|
||||
start-stop-daemon --stop --signal HUP --pidfile $PIDFILE
|
||||
if [ $? -ne 0 ]; then
|
||||
fail "failed to reload"
|
||||
else
|
||||
ok "reloaded"
|
||||
fi
|
||||
}
|
||||
|
||||
# Starts the service.
|
||||
start_service()
|
||||
{
|
||||
# Check if config file exist
|
||||
if [ ! -r $CONFIG ]; then
|
||||
fail "config file doesn't exist (or you don't have permission to view)"
|
||||
return 1
|
||||
fi
|
||||
# Bump the file limits, before launching the daemon. These will carry over to
|
||||
# launched processes.
|
||||
ulimit -n $OPEN_FILE_LIMIT
|
||||
if [ $? -ne 0 ]; then
|
||||
fail "set open file limit to $OPEN_FILE_LIMIT"
|
||||
return 1
|
||||
fi
|
||||
start-stop-daemon --start --chuid $GROUP:$USER --quiet --background --make-pidfile --pidfile $PIDFILE --startas /bin/bash -- -c "exec $DAEMON -c $CONFIG $CENTRIFUGO_OPTS >> $STDOUT 2>&1"
|
||||
if [ $? -ne 0 ]; then
|
||||
fail "failed to start"
|
||||
else
|
||||
ok "started"
|
||||
fi
|
||||
}
|
||||
|
||||
# Stops the service.
|
||||
stop_service() {
|
||||
start-stop-daemon --stop --signal QUIT --retry=TERM/30/KILL/5 --pidfile $PIDFILE
|
||||
if [ $? -ne 0 ]; then
|
||||
fail "failed to stop"
|
||||
else
|
||||
ok "stopped"
|
||||
fi
|
||||
}
|
||||
|
||||
# Load the LSB log_* functions.
|
||||
INCLUDE=/lib/lsb/init-functions
|
||||
if [ -r "${INCLUDE}" ]
|
||||
then
|
||||
. "${INCLUDE}"
|
||||
else
|
||||
"${PRINT}" '%s: unable to load LSB functions, cannot start service.\n' "${NAME}" 1>&2
|
||||
exit ${EC_DAEMON_NOT_FOUND}
|
||||
fi
|
||||
|
||||
# Make sure only one argument was passed to the script.
|
||||
if [ $# -ne 1 ]
|
||||
then
|
||||
if [ $# -lt 1 -o "$1" = '' ]
|
||||
then fail 'action not specified.'
|
||||
else fail 'too many arguments.'
|
||||
fi
|
||||
usage 1>&2
|
||||
die ${EC_INVALID_ARGUMENT}
|
||||
fi
|
||||
readonly ACTION="$1"
|
||||
|
||||
# Check if daemon is a recognized program and get absolute path.
|
||||
readonly DAEMON=$("${WHICH}" "${NAME}")
|
||||
if [ ! -x "${DAEMON}" ]
|
||||
then
|
||||
if [ "${ACTION}" = 'stop' ]
|
||||
then
|
||||
fail 'executable not found: stop request ignored'
|
||||
end
|
||||
else
|
||||
fail "executable not found: cannot ${ACTION} service"
|
||||
die ${EC_DAEMON_NOT_FOUND}
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check start-stop-daemon
|
||||
readonly SSD=$("${WHICH}" "start-stop-daemon")
|
||||
if [ ! -x "${SSD}" ]
|
||||
then
|
||||
fail "start-stop-daemon required"
|
||||
end
|
||||
fi
|
||||
|
||||
# Determine the service's status.
|
||||
start-stop-daemon --status --pidfile "${PIDFILE}" 2>/dev/null 1>/dev/null
|
||||
readonly STATUS=$?
|
||||
|
||||
configtest() {
|
||||
$DAEMON checkconfig -c $CONFIG
|
||||
}
|
||||
|
||||
status_q() {
|
||||
status_of_proc "${DAEMON}" "${NAME}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
case $1 in
|
||||
start)
|
||||
if [ ${STATUS} -eq 0 ]
|
||||
then
|
||||
ok 'already started.'
|
||||
else
|
||||
start_service || die ${EC_START_FAILED}
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ ${STATUS} -eq 0 ]
|
||||
then
|
||||
stop_service || die ${EC_STOP_FAILED}
|
||||
else
|
||||
ok 'already stopped.'
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
if [ ${STATUS} -eq 0 ]
|
||||
then
|
||||
stop_service || die ${EC_RESTART_STOP_FAILED}
|
||||
sleep 1
|
||||
fi
|
||||
start_service || die ${EC_RESTART_START_FAILED}
|
||||
;;
|
||||
configtest)
|
||||
$1
|
||||
;;
|
||||
status)
|
||||
status_of_proc "${DAEMON}" "${NAME}" || exit $?
|
||||
;;
|
||||
reload)
|
||||
if [ ${STATUS} -eq 0 ]
|
||||
then
|
||||
configtest
|
||||
if [[ $? -ne 0 ]]; then
|
||||
die ${EC_RELOADING_FAILED}
|
||||
fi
|
||||
reload_service || die ${EC_RELOADING_FAILED}
|
||||
else
|
||||
fail 'service not running, nothing to be done.'
|
||||
fi
|
||||
;;
|
||||
version)
|
||||
$DAEMON version
|
||||
;;
|
||||
condrestart)
|
||||
status_q && $0 restart || exit 0
|
||||
;;
|
||||
*)
|
||||
# For invalid arguments, print the usage message.
|
||||
echo "Usage: $0 {start|stop|restart|condrestart|reload|configtest|status|version}"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
8
misc/packaging/logrotate
Normal file
8
misc/packaging/logrotate
Normal file
@@ -0,0 +1,8 @@
|
||||
/var/log/centrifugo/centrifugo.log {
|
||||
daily
|
||||
rotate 7
|
||||
missingok
|
||||
dateext
|
||||
copytruncate
|
||||
compress
|
||||
}
|
||||
100
misc/packaging/post_install.sh
Normal file
100
misc/packaging/post_install.sh
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
|
||||
BIN_DIR=/usr/bin
|
||||
DATA_DIR=/var/lib/centrifugo
|
||||
LOG_DIR=/var/log/centrifugo
|
||||
SCRIPT_DIR=/usr/lib/centrifugo/scripts
|
||||
|
||||
function install_init {
|
||||
cp -f $SCRIPT_DIR/initd.sh /etc/init.d/centrifugo
|
||||
chmod +x /etc/init.d/centrifugo
|
||||
}
|
||||
|
||||
function install_initel6 {
|
||||
cp -f $SCRIPT_DIR/initd.el6.sh /etc/init.d/centrifugo
|
||||
chmod +x /etc/init.d/centrifugo
|
||||
}
|
||||
|
||||
function install_systemd {
|
||||
cp -f $SCRIPT_DIR/centrifugo.service /lib/systemd/system/centrifugo.service
|
||||
systemctl is-enabled centrifugo &>/dev/null
|
||||
if [[ $? -ne 0 ]]; then
|
||||
systemctl enable centrifugo &>/dev/null
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
function install_upstart {
|
||||
cp -f $SCRIPT_DIR/centrifugo.upstart /etc/init/centrifugo.conf
|
||||
initctl reload-configuration
|
||||
}
|
||||
|
||||
function install_update_rcd {
|
||||
update-rc.d centrifugo defaults
|
||||
}
|
||||
|
||||
function install_chkconfig {
|
||||
chkconfig --add centrifugo
|
||||
}
|
||||
|
||||
id centrifugo &>/dev/null
|
||||
if [[ $? -ne 0 ]]; then
|
||||
useradd --system -U -M centrifugo -s /bin/false -d $DATA_DIR
|
||||
fi
|
||||
|
||||
if [ ! -d $DATA_DIR ]; then
|
||||
mkdir -p $DATA_DIR
|
||||
fi
|
||||
|
||||
if [ ! -d $LOG_DIR ]; then
|
||||
mkdir -p $LOG_DIR
|
||||
fi
|
||||
|
||||
chown -R -L centrifugo:centrifugo $DATA_DIR
|
||||
chown -R -L centrifugo:centrifugo $LOG_DIR
|
||||
|
||||
# Add defaults file, if it doesn't exist
|
||||
if [[ ! -f /etc/default/centrifugo ]]; then
|
||||
touch /etc/default/centrifugo
|
||||
fi
|
||||
|
||||
# Generate config with unique secret key, if it doesn't exist
|
||||
if [[ ! -f /etc/centrifugo/config.json ]]; then
|
||||
/usr/bin/centrifugo genconfig -c /etc/centrifugo/config.json
|
||||
fi
|
||||
|
||||
# Distribution-specific logic
|
||||
if [[ -f /etc/redhat-release ]]; then
|
||||
# RHEL-variant logic
|
||||
which systemctl &>/dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
install_systemd
|
||||
else
|
||||
# Assuming sysv
|
||||
install_initel6
|
||||
install_chkconfig
|
||||
fi
|
||||
elif [[ -f /etc/debian_version ]]; then
|
||||
# Debian/Ubuntu logic
|
||||
which systemctl &>/dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
install_systemd
|
||||
else
|
||||
/sbin/init --version >/dev/null 2>/dev/null
|
||||
if [[ $? -eq 0 && `/sbin/init --version` =~ upstart ]]; then
|
||||
# Assuming upstart
|
||||
install_upstart
|
||||
else
|
||||
# Assuming sysv
|
||||
install_init
|
||||
install_update_rcd
|
||||
fi
|
||||
fi
|
||||
elif [[ -f /etc/os-release ]]; then
|
||||
source /etc/os-release
|
||||
if [[ $ID = "amzn" ]]; then
|
||||
# Amazon Linux logic
|
||||
install_init
|
||||
install_chkconfig
|
||||
fi
|
||||
fi
|
||||
76
misc/packaging/post_uninstall.sh
Normal file
76
misc/packaging/post_uninstall.sh
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
|
||||
function disable_systemd {
|
||||
systemctl is-active centrifugo &>/dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
systemctl stop centrifugo || :
|
||||
fi
|
||||
systemctl is-enabled centrifugo &>/dev/null
|
||||
if [[ $? -ne 0 ]]; then
|
||||
systemctl disable centrifugo &>/dev/null
|
||||
fi
|
||||
rm -f /lib/systemd/system/centrifugo.service
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
function disable_upstart {
|
||||
initctl stop centrifugo || :
|
||||
rm -f /etc/init/centrifugo.conf
|
||||
initctl reload-configuration
|
||||
}
|
||||
|
||||
function disable_update_rcd {
|
||||
service centrifugo stop || :
|
||||
update-rc.d -f centrifugo remove
|
||||
rm -f /etc/init.d/centrifugo
|
||||
}
|
||||
|
||||
function disable_chkconfig {
|
||||
service centrifugo stop || :
|
||||
chkconfig --del centrifugo
|
||||
rm -f /etc/init.d/centrifugo
|
||||
}
|
||||
|
||||
if [[ -f /etc/redhat-release ]]; then
|
||||
# RHEL-variant logic
|
||||
if [[ "$1" = "0" ]]; then
|
||||
# Centrifugo is no longer installed, remove from init system
|
||||
which systemctl &>/dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
disable_systemd
|
||||
else
|
||||
# Assuming sysv
|
||||
disable_chkconfig
|
||||
fi
|
||||
rm -f /etc/default/centrifugo
|
||||
fi
|
||||
elif [[ -f /etc/debian_version ]]; then
|
||||
# Debian/Ubuntu logic
|
||||
if [[ "$1" != "upgrade" ]]; then
|
||||
# Remove/purge
|
||||
which systemctl &>/dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
disable_systemd
|
||||
else
|
||||
/sbin/init --version >/dev/null 2>/dev/null
|
||||
if [[ $? -eq 0 && `/sbin/init --version` =~ upstart ]]; then
|
||||
# Assuming upstart
|
||||
disable_upstart
|
||||
else
|
||||
# Assuming sysv
|
||||
disable_update_rcd
|
||||
fi
|
||||
fi
|
||||
rm -f /etc/default/centrifugo
|
||||
fi
|
||||
elif [[ -f /etc/os-release ]]; then
|
||||
source /etc/os-release
|
||||
if [[ $ID = "amzn" ]]; then
|
||||
# Amazon Linux logic
|
||||
if [[ "$1" = "0" ]]; then
|
||||
# Centrifugo is no longer installed, remove from init system
|
||||
disable_chkconfig
|
||||
rm -f /etc/default/centrifugo
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
1
misc/packaging/pre_install.sh
Normal file
1
misc/packaging/pre_install.sh
Normal file
@@ -0,0 +1 @@
|
||||
#!/bin/bash
|
||||
3
misc/postgres/init-replication.sh
Executable file
3
misc/postgres/init-replication.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
echo "host replication all 0.0.0.0/0 trust" >> "$PGDATA/pg_hba.conf"
|
||||
pg_ctl reload
|
||||
102
misc/redis_cluster/create_cluster.sh
Normal file
102
misc/redis_cluster/create_cluster.sh
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to wait for Redis node to become available
|
||||
wait_for_redis() {
|
||||
local port=$1
|
||||
while ! redis-cli -p "$port" ping > /dev/null 2>&1; do
|
||||
echo "Waiting for Redis node on port $port to start..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis node on port $port is available."
|
||||
}
|
||||
|
||||
# Array to hold PIDs of Redis server processes
|
||||
declare -a redis_pids
|
||||
|
||||
# Function to start Redis cluster nodes
|
||||
start_redis_cluster() {
|
||||
local masters=$1
|
||||
local replicas=$2
|
||||
local base_port=7001
|
||||
local cluster_dir="./cluster_data"
|
||||
local total_nodes=$((masters * (replicas + 1)))
|
||||
local redis_nodes=()
|
||||
|
||||
# Create cluster directory, clean previous.
|
||||
rm -r "$cluster_dir"
|
||||
mkdir -p "$cluster_dir"
|
||||
|
||||
# Start master nodes
|
||||
for ((i=0; i<masters; i++)); do
|
||||
local port=$((base_port + i))
|
||||
local node_dir="$cluster_dir/$port"
|
||||
mkdir -p "$node_dir"
|
||||
pushd "$node_dir" > /dev/null
|
||||
|
||||
# Start a Redis server with cluster enabled and no replicas
|
||||
redis-server --port "$port" --cluster-enabled yes \
|
||||
--cluster-config-file "nodes.conf" \
|
||||
--cluster-node-timeout 5000 --save "" --appendonly no \
|
||||
--appendfilename "appendonly.aof" \
|
||||
--dbfilename "dump.rdb" \
|
||||
--logfile "redis.log" &
|
||||
redis_pids[i]=$!
|
||||
popd > /dev/null
|
||||
redis_nodes+=("127.0.0.1:$port")
|
||||
done
|
||||
|
||||
# Start replica nodes
|
||||
for ((i=0; i<masters * replicas; i++)); do
|
||||
local port=$((base_port + masters + i))
|
||||
local node_dir="$cluster_dir/$port"
|
||||
mkdir -p "$node_dir"
|
||||
pushd "$node_dir" > /dev/null
|
||||
|
||||
# Start a Redis server with cluster enabled and no replicas
|
||||
redis-server --port "$port" --cluster-enabled yes \
|
||||
--cluster-config-file "nodes.conf" \
|
||||
--cluster-node-timeout 5000 --save "" --appendonly no \
|
||||
--appendfilename "appendonly.aof" \
|
||||
--dbfilename "dump.rdb" \
|
||||
--logfile "redis.log" &
|
||||
local pid=$!
|
||||
redis_pids+=("$pid")
|
||||
popd > /dev/null
|
||||
redis_nodes+=("127.0.0.1:$port")
|
||||
done
|
||||
|
||||
# Wait for all nodes to become available
|
||||
for port in "${redis_nodes[@]}"; do
|
||||
wait_for_redis "${port##*:}"
|
||||
done
|
||||
|
||||
# Create the cluster with replicas
|
||||
echo "Creating Redis cluster with the following nodes: ${redis_nodes[*]}"
|
||||
yes "yes" | redis-cli --cluster create "${redis_nodes[@]}" --cluster-replicas "$replicas"
|
||||
}
|
||||
|
||||
# Function to shut down Redis cluster nodes
|
||||
shutdown_redis_cluster() {
|
||||
echo "Shutting down Redis cluster nodes..."
|
||||
for pid in "${redis_pids[@]}"; do
|
||||
kill "$pid"
|
||||
done
|
||||
}
|
||||
|
||||
# Trap Ctrl+C (SIGINT) to call shutdown function
|
||||
trap shutdown_redis_cluster SIGINT
|
||||
|
||||
# Check if the number of masters and replicas is provided
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
echo "Usage: $0 <number_of_masters> <number_of_replicas_per_master>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start the Redis cluster
|
||||
start_redis_cluster "$1" "$2"
|
||||
|
||||
# Wait until a keyboard interrupt (Ctrl+C) is detected
|
||||
echo "Redis cluster is running. Press Ctrl+C to stop."
|
||||
wait
|
||||
|
||||
# The script will wait at the above line until the user presses Ctrl+C, which will trigger the shutdown function
|
||||
9
misc/redis_cluster/readme.md
Normal file
9
misc/redis_cluster/readme.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Create local Redis cluster consisting of N nodes with M replicas each, ex:
|
||||
|
||||
```
|
||||
bash create_cluster.sh 3 0
|
||||
```
|
||||
|
||||
Cluster nodes will work until script interrupted. The goal of having this script is for development and benchmarking.
|
||||
|
||||
All data will be saved to local directory `cluster_data`. By default, Redis runs without RDB and AOF (`--save "" --appendonly no`).
|
||||
9
misc/redis_instances/readme.md
Normal file
9
misc/redis_instances/readme.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Create local N isolated Redis master instances starting from port `6000`:
|
||||
|
||||
```
|
||||
bash start_instances.sh 3
|
||||
```
|
||||
|
||||
Redis nodes will work until script interrupted. The goal of having this script is for development and benchmarking.
|
||||
|
||||
By default, Redis runs without RDB and AOF (`--save "" --appendonly no`).
|
||||
75
misc/redis_instances/start_instances.sh
Normal file
75
misc/redis_instances/start_instances.sh
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to wait for Redis node to become available
|
||||
wait_for_redis() {
|
||||
local port=$1
|
||||
while ! redis-cli -p "$port" ping > /dev/null 2>&1; do
|
||||
echo "Waiting for Redis node on port $port to start..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis node on port $port is available."
|
||||
}
|
||||
|
||||
# Array to hold PIDs of Redis server processes
|
||||
declare -a redis_pids
|
||||
|
||||
# Function to start Redis nodes
|
||||
start_redis_instances() {
|
||||
local masters=$1
|
||||
local base_port=6000
|
||||
local redis_dir="./redis_data"
|
||||
local total_nodes=$((masters * (0 + 1)))
|
||||
local redis_nodes=()
|
||||
|
||||
# Create cluster directory, clean previous.
|
||||
rm -r "$redis_dir"
|
||||
mkdir -p "$redis_dir"
|
||||
|
||||
# Start master nodes
|
||||
for ((i=0; i<masters; i++)); do
|
||||
local port=$((base_port + i))
|
||||
local node_dir="$redis_dir/$port"
|
||||
mkdir -p "$node_dir"
|
||||
pushd "$node_dir" > /dev/null
|
||||
|
||||
redis-server --port "$port" \
|
||||
--save "" --appendonly no \
|
||||
--appendfilename "appendonly.aof" \
|
||||
--dbfilename "dump.rdb" \
|
||||
--logfile "redis.log" &
|
||||
redis_pids[i]=$!
|
||||
popd > /dev/null
|
||||
redis_nodes+=("127.0.0.1:$port")
|
||||
done
|
||||
|
||||
# Wait for all nodes to become available
|
||||
for port in "${redis_nodes[@]}"; do
|
||||
wait_for_redis "${port##*:}"
|
||||
done
|
||||
}
|
||||
|
||||
# Function to shut down Redis nodes
|
||||
shutdown_redis_instances() {
|
||||
echo "Shutting down Redis nodes..."
|
||||
for pid in "${redis_pids[@]}"; do
|
||||
kill "$pid"
|
||||
done
|
||||
}
|
||||
|
||||
# Trap Ctrl+C (SIGINT) to call shutdown function
|
||||
trap shutdown_redis_instances SIGINT
|
||||
|
||||
# Check if the number of masters and replicas is provided
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <number_of_masters>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start Redis instances
|
||||
start_redis_instances "$1"
|
||||
|
||||
# Wait until a keyboard interrupt (Ctrl+C) is detected
|
||||
echo "Redis instances are running. Press Ctrl+C to stop."
|
||||
wait
|
||||
|
||||
# The script will wait at the above line until the user presses Ctrl+C, which will trigger the shutdown function
|
||||
21
misc/redis_sentinel/readme.md
Normal file
21
misc/redis_sentinel/readme.md
Normal file
@@ -0,0 +1,21 @@
|
||||
Create local Sentinel based Redis setup consisting of 1 Sentinel and 3 Redis nodes (master, 2 replicas). Note, in production you need at least 3 Sentinels for high availability.
|
||||
|
||||
## Run
|
||||
|
||||
```
|
||||
bash start.sh
|
||||
```
|
||||
|
||||
## Emulate failover
|
||||
|
||||
Connect to Sentinel:
|
||||
|
||||
```
|
||||
redis-cli -p 26379
|
||||
```
|
||||
|
||||
And run:
|
||||
|
||||
```
|
||||
sentinel failover mymaster
|
||||
```
|
||||
94
misc/redis_sentinel/start.sh
Normal file
94
misc/redis_sentinel/start.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to wait for Redis node to become available
|
||||
wait_for_redis() {
|
||||
local port=$1
|
||||
while ! redis-cli -p "$port" ping > /dev/null 2>&1; do
|
||||
echo "Waiting for Redis node on port $port to start..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis node on port $port is available."
|
||||
}
|
||||
|
||||
# Array to hold PIDs of Redis server processes
|
||||
declare -a redis_pids
|
||||
|
||||
# Function to start Redis Sentinel setup
|
||||
start_redis_sentinel() {
|
||||
local base_port=7000
|
||||
local sentinel_port=26379
|
||||
local data_dir="./sentinel_data"
|
||||
|
||||
# Clean up previous setup
|
||||
rm -rf "$data_dir"
|
||||
mkdir -p "$data_dir"
|
||||
|
||||
# Start the master node
|
||||
local master_port=$((base_port))
|
||||
local master_dir="$data_dir/master"
|
||||
mkdir -p "$master_dir"
|
||||
pushd "$master_dir" > /dev/null
|
||||
redis-server --port "$master_port" --save "" --appendonly no --dbfilename "dump.rdb" --logfile "redis.log" &
|
||||
local pid=$!
|
||||
redis_pids+=($pid)
|
||||
echo "Master started on port $master_port with PID $pid"
|
||||
popd > /dev/null
|
||||
|
||||
# Start the replicas
|
||||
for i in 1 2; do
|
||||
local replica_port=$((base_port + i))
|
||||
local replica_dir="$data_dir/replica_$i"
|
||||
mkdir -p "$replica_dir"
|
||||
pushd "$replica_dir" > /dev/null
|
||||
redis-server --port "$replica_port" --slaveof 127.0.0.1 "$master_port" --save "" --appendonly no --dbfilename "dump.rdb" --logfile "redis.log" &
|
||||
pid=$!
|
||||
redis_pids+=($pid)
|
||||
echo "Replica $i started on port $replica_port with PID $pid"
|
||||
popd > /dev/null
|
||||
done
|
||||
|
||||
# Wait for all nodes to become available
|
||||
wait_for_redis "$master_port"
|
||||
for i in 1 2; do
|
||||
wait_for_redis $((base_port + i))
|
||||
done
|
||||
|
||||
# Start Redis Sentinel
|
||||
local sentinel_dir="$data_dir/sentinel"
|
||||
mkdir -p "$sentinel_dir"
|
||||
pushd "$sentinel_dir" > /dev/null
|
||||
|
||||
cat <<EOF > sentinel.conf
|
||||
port $sentinel_port
|
||||
sentinel monitor mymaster 127.0.0.1 $master_port 1
|
||||
sentinel down-after-milliseconds mymaster 5000
|
||||
sentinel failover-timeout mymaster 10000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
EOF
|
||||
|
||||
redis-server sentinel.conf --sentinel &
|
||||
pid=$!
|
||||
redis_pids+=($pid)
|
||||
echo "Sentinel started on port $sentinel_port with PID $pid"
|
||||
popd > /dev/null
|
||||
}
|
||||
|
||||
# Function to shut down Redis nodes and Sentinel
|
||||
shutdown_redis_sentinel() {
|
||||
echo "Shutting down Redis nodes and Sentinel..."
|
||||
for pid in "${redis_pids[@]}"; do
|
||||
kill "$pid"
|
||||
echo "Stopped process with PID $pid"
|
||||
done
|
||||
}
|
||||
|
||||
# Trap Ctrl+C (SIGINT) to call shutdown function
|
||||
trap shutdown_redis_sentinel SIGINT
|
||||
|
||||
# Start the Redis Sentinel setup
|
||||
start_redis_sentinel
|
||||
|
||||
# Wait until a keyboard interrupt (Ctrl+C) is detected
|
||||
echo "Redis Sentinel setup is running. Press Ctrl+C to stop."
|
||||
echo "All PIDs: ${redis_pids[*]}"
|
||||
wait
|
||||
21
misc/release/notes.md
Normal file
21
misc/release/notes.md
Normal file
@@ -0,0 +1,21 @@
|
||||
Centrifugo is an open-source scalable real-time messaging server. It instantly delivers messages to application online users connected over supported transports (WebSocket, HTTP-streaming, Server-Sent Events (SSE), GRPC, WebTransport). Centrifugo is built around channel subscriptions – so it's a user-facing PUB/SUB server.
|
||||
|
||||
Centrifugo is language-agnostic and can be used to build chat apps, live comments, multiplayer games, real-time data visualizations, collaborative tools, AI streaming responses, etc. in combination with any backend. It is well suited for modern architectures and allows decoupling the business logic from the real-time transport layer.
|
||||
|
||||
Official client SDKs are available for JavaScript (browser, Node.js, React Native), Dart/Flutter, Swift, Java, Python, Go, and .NET. In addition, Centrifugo supports a unidirectional approach for simple use cases with no SDK dependency.
|
||||
|
||||
For details, go to the [Centrifugo documentation site](https://centrifugal.dev). For runnable demos see [centrifugal/examples](https://github.com/centrifugal/examples).
|
||||
|
||||
## What's changed
|
||||
|
||||
### Fixes
|
||||
|
||||
* A client-side subscription refresh with an already expired subscription token was accepted. The subscription's expiration time was then cleared, so the subscription never expired. Now Centrifugo closes the connection with the `3006` (`subscription expired`) disconnect code. The client reconnects, gets a token expired error on resubscribe and requests a new token – all official SDKs already handle this ([centrifugal/centrifuge#627](https://github.com/centrifugal/centrifuge/pull/627)).
|
||||
* Fossil delta compression with recovery: when a subscribe with recovery found no missed publications, the next publication was still sent as a delta. The client had no base data for it, so it could not decode that publication or any after it. Now the first publication after such a subscribe is sent with full data ([centrifugal/centrifuge#629](https://github.com/centrifugal/centrifuge/pull/629)).
|
||||
* Align fossil deltas to UTF-8 character boundaries for JSON clients [centrifugal/centrifuge#630](https://github.com/centrifugal/centrifuge/pull/630). For a change inside a multi-byte character it copies the first bytes of the character from the previous data and inserts the rest. The inserted bytes aren't valid UTF-8, json.Escape replaces them with U+FFFD, and the client fails to apply the delta. The fix eliminates this.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* This release is built with Go 1.26.8.
|
||||
* Dependency updates.
|
||||
* See also the corresponding [Centrifugo PRO release](https://github.com/centrifugal/centrifugo-pro/releases/tag/v6.9.6).
|
||||
21
misc/rpm/README.md
Normal file
21
misc/rpm/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
RPM for Centrifugo
|
||||
|
||||
```
|
||||
yum -y install rpmdevtools
|
||||
```
|
||||
|
||||
Go to directory containing all files listed here.
|
||||
|
||||
Download release to current folder:
|
||||
|
||||
```
|
||||
wget https://github.com/centrifugal/centrifugo/releases/download/v0.0.1/centrifugo-0.0.1-linux-amd64.zip -O centrifugo-0.0.1-linux-amd64.zip
|
||||
```
|
||||
|
||||
Build rpm:
|
||||
|
||||
```
|
||||
./build.sh 0.0.1
|
||||
```
|
||||
|
||||
As argument to `build.sh` you should specify Centrifugo version of release binary downloaded above.
|
||||
20
misc/rpm/build.sh
Executable file
20
misc/rpm/build.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
if [ "$1" = "" ]
|
||||
then
|
||||
echo "Usage: $0 <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f centrifugo-$1-linux-amd64.zip ]; then
|
||||
echo "No Centrifugo release found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp centrifugo-$1-linux-amd64.zip ~/rpmbuild/SOURCES/centrifugo-$1-linux-amd64.zip
|
||||
cp centrifugo.spec ~/rpmbuild/SPECS/centrifugo.spec
|
||||
cp centrifugo.initd ~/rpmbuild/SOURCES/centrifugo.initd
|
||||
cp centrifugo.nofiles.conf ~/rpmbuild/SOURCES/centrifugo.nofiles.conf
|
||||
cp centrifugo.logrotate ~/rpmbuild/SOURCES/centrifugo.logrotate
|
||||
cp centrifugo.config.json ~/rpmbuild/SOURCES/centrifugo.config.json
|
||||
|
||||
rpmbuild -bb ~/rpmbuild/SPECS/centrifugo.spec --define "version $1" --define "release `date +%s`"
|
||||
1
misc/rpm/centrifugo.config.json
Normal file
1
misc/rpm/centrifugo.config.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
148
misc/rpm/centrifugo.initd
Normal file
148
misc/rpm/centrifugo.initd
Normal file
@@ -0,0 +1,148 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# /etc/init.d/centrifugo
|
||||
#
|
||||
# Startup script for centrifugo
|
||||
#
|
||||
# chkconfig: 2345 20 80
|
||||
# description: Starts and stops centrifugo
|
||||
|
||||
# Source function library.
|
||||
if [ -f /etc/init.d/functions ] ; then
|
||||
. /etc/init.d/functions
|
||||
elif [ -f /etc/rc.d/init.d/functions ] ; then
|
||||
. /etc/rc.d/init.d/functions
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Daemon options
|
||||
CENTRIFUGO_OPTS=
|
||||
|
||||
# Process name
|
||||
NAME=centrifugo
|
||||
|
||||
# User and group
|
||||
USER=centrifugo
|
||||
GROUP=centrifugo
|
||||
|
||||
# Daemon name, where is the actual executable
|
||||
# If the daemon is not there, then exit.
|
||||
DAEMON=/usr/sbin/centrifugo
|
||||
|
||||
# Configuration file
|
||||
CONFIG=/etc/centrifugo/config.json
|
||||
|
||||
# Logging
|
||||
if [ -z "$STDOUT" ]; then
|
||||
STDOUT=/var/log/centrifugo/centrifugo.log
|
||||
fi
|
||||
|
||||
if [ ! -f "$STDOUT" ]; then
|
||||
mkdir -p $(dirname $STDOUT)
|
||||
fi
|
||||
|
||||
if [ -z "$STDERR" ]; then
|
||||
STDERR=/var/log/centrifugo/centrifugo.log
|
||||
fi
|
||||
|
||||
if [ ! -f "$STDERR" ]; then
|
||||
mkdir -p $(dirname $STDERR)
|
||||
fi
|
||||
|
||||
[ -e /etc/sysconfig/centrifugo ] && . /etc/sysconfig/centrifugo
|
||||
|
||||
lockfile="/var/lock/subsys/centrifugo"
|
||||
|
||||
if ! [ -x $DAEMON ]; then
|
||||
echo "$DAEMON binary not found."
|
||||
exit 5
|
||||
fi
|
||||
|
||||
start() {
|
||||
if ! [ -f $CONFIG ]; then
|
||||
echo "configuration file not found $CONFIG"
|
||||
exit 6
|
||||
fi
|
||||
configtest || return $?
|
||||
echo -n $"Starting $NAME: "
|
||||
daemon --user centrifugo --check=$DAEMON "nohup $DAEMON --config=$CONFIG $CENTRIFUGO_OPTS >>$STDOUT 2>&1 &"
|
||||
retval=$?
|
||||
echo
|
||||
[ $retval -eq 0 ] && touch $lockfile
|
||||
return $retval
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping $NAME: "
|
||||
killproc $NAME -TERM
|
||||
retval=$?
|
||||
if [ $retval -eq 0 ]; then
|
||||
if [ "$CONSOLETYPE" != "serial" ]; then
|
||||
echo -en "\\033[16G"
|
||||
fi
|
||||
while rh_status_q
|
||||
do
|
||||
sleep 1
|
||||
echo -n $"."
|
||||
done
|
||||
rm -f $lockfile
|
||||
fi
|
||||
echo
|
||||
return $retval
|
||||
}
|
||||
|
||||
restart() {
|
||||
configtest || return $?
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
reload() {
|
||||
configtest || return $?
|
||||
echo -n $"Reloading $NAME: "
|
||||
killproc $DAEMON -HUP
|
||||
sleep 1
|
||||
RETVAL=$?
|
||||
echo
|
||||
}
|
||||
|
||||
configtest() {
|
||||
$DAEMON checkconfig -c $CONFIG
|
||||
}
|
||||
|
||||
rh_status() {
|
||||
status $NAME
|
||||
}
|
||||
|
||||
rh_status_q() {
|
||||
rh_status >/dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
rh_status_q && exit 0
|
||||
$1
|
||||
;;
|
||||
stop)
|
||||
rh_status_q || exit 0
|
||||
$1
|
||||
;;
|
||||
restart|configtest)
|
||||
$1
|
||||
;;
|
||||
reload)
|
||||
rh_status_q || exit 7
|
||||
$1
|
||||
;;
|
||||
status|status_q)
|
||||
rh_$1
|
||||
;;
|
||||
condrestart)
|
||||
rh_status_q && restart || exit 0
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 {start|stop|reload|status|restart|configtest|condrestart}"
|
||||
exit 2
|
||||
esac
|
||||
7
misc/rpm/centrifugo.logrotate
Normal file
7
misc/rpm/centrifugo.logrotate
Normal file
@@ -0,0 +1,7 @@
|
||||
/var/log/centrifugo/*log {
|
||||
copytruncate
|
||||
daily
|
||||
rotate 7
|
||||
compress
|
||||
missingok
|
||||
}
|
||||
1
misc/rpm/centrifugo.nofiles.conf
Normal file
1
misc/rpm/centrifugo.nofiles.conf
Normal file
@@ -0,0 +1 @@
|
||||
centrifugo - nofile 65536
|
||||
92
misc/rpm/centrifugo.spec
Normal file
92
misc/rpm/centrifugo.spec
Normal file
@@ -0,0 +1,92 @@
|
||||
%define debug_package %{nil}
|
||||
%define application_user %{name}
|
||||
%define application_group %{name}
|
||||
%define application_data %{_localstatedir}/lib/%{name}
|
||||
%define __prefix /opt
|
||||
|
||||
Name: centrifugo
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
Summary: Real-time messaging server
|
||||
License: MIT
|
||||
URL: https://github.com/centrifugal/centrifugo
|
||||
Group: Apps/sys
|
||||
Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n)
|
||||
Packager: Alexandr Emelin <frvzmb@gmail.com>
|
||||
Requires(pre): shadow-utils
|
||||
Requires(post): /sbin/chkconfig
|
||||
Requires(preun): /sbin/chkconfig, /sbin/service
|
||||
Requires(postun): /sbin/service
|
||||
|
||||
%description
|
||||
Real-time messaging server written in Go
|
||||
|
||||
%prep
|
||||
cd ~/rpmbuild/BUILD
|
||||
rm -rf %{name}-%{version}-linux-amd64
|
||||
unzip ~/rpmbuild/SOURCES/%{name}-%{version}-linux-amd64.zip
|
||||
if [ $? -ne 0 ]; then
|
||||
exit $?
|
||||
fi
|
||||
ls
|
||||
cd %{name}-%{version}-linux-amd64
|
||||
chown -R $USER.$USER .
|
||||
chmod -R a+rX,g-w,o-w .
|
||||
|
||||
%build
|
||||
rm -rf %{buildroot}
|
||||
|
||||
echo %{buildroot}
|
||||
|
||||
%install
|
||||
install -d -m 755 %{buildroot}/%{_sbindir}
|
||||
install -m 755 %{_builddir}/%{name}-%{version}-linux-amd64/centrifugo %{buildroot}/%{_sbindir}
|
||||
|
||||
install -d -m 755 %{buildroot}%{__prefix}/%{name}
|
||||
|
||||
install -d -m 755 %{buildroot}/%{_localstatedir}/log/%{name}
|
||||
install -d -m 755 %{buildroot}/%{_localstatedir}/lib/%{name}
|
||||
|
||||
install -d -m 755 %{buildroot}/%{_initrddir}
|
||||
install -m 755 %_sourcedir/%{name}.initd %{buildroot}/%{_initrddir}/%{name}
|
||||
|
||||
install -d -m 755 %{buildroot}/%{_sysconfdir}/security/limits.d/
|
||||
install -m 644 %_sourcedir/%{name}.nofiles.conf %{buildroot}/%{_sysconfdir}/security/limits.d/%{name}.nofiles.conf
|
||||
|
||||
install -d -m 755 %{buildroot}/%{_sysconfdir}/logrotate.d
|
||||
install -m 644 %_sourcedir/%{name}.logrotate %{buildroot}/%{_sysconfdir}/logrotate.d/%{name}
|
||||
|
||||
install -d -m 755 %{buildroot}/%{_sysconfdir}/%{name}/
|
||||
install -m 644 %_sourcedir/%{name}.config.json %{buildroot}/%{_sysconfdir}/%{name}/config.json
|
||||
|
||||
%clean
|
||||
rm -rf %{buildroot}
|
||||
|
||||
%pre
|
||||
getent group %{application_group} >/dev/null || groupadd -r %{application_group}
|
||||
getent passwd %{application_user} >/dev/null || /usr/sbin/useradd --comment "centrifugo Daemon User" --shell /bin/bash -M -r -g %{application_group} --home %{application_data} %{application_user}
|
||||
|
||||
%post
|
||||
chkconfig --add %{name}
|
||||
|
||||
%preun
|
||||
if [ $1 = 0 ]; then
|
||||
service %{name} stop > /dev/null 2>&1 || :
|
||||
chkconfig --del %{name}
|
||||
fi
|
||||
|
||||
%postun
|
||||
if [ $1 == 1 ] ; then
|
||||
service %{name} condrestart >/dev/null 2>&1 || :
|
||||
fi
|
||||
|
||||
%files
|
||||
%defattr(-,root,root)
|
||||
%{_sbindir}/%{name}
|
||||
%attr(0755,%{application_user},%{application_group}) %dir %{_localstatedir}/log/%{name}
|
||||
%attr(0755,%{application_user},%{application_group}) %dir %{_localstatedir}/lib/%{name}
|
||||
%{_initrddir}/%{name}
|
||||
%{__prefix}/%{name}
|
||||
%config(noreplace) %{_sysconfdir}/%{name}/config.json
|
||||
%config(noreplace) %{_sysconfdir}/security/limits.d/%{name}.nofiles.conf
|
||||
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
|
||||
17
misc/scripts/generate.sh
Normal file
17
misc/scripts/generate.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
set -e
|
||||
|
||||
cd internal/apiproto
|
||||
bash generate.sh
|
||||
cd -
|
||||
|
||||
cd internal/apiproto/swagger
|
||||
bash generate_swagger.sh
|
||||
cd -
|
||||
|
||||
cd internal/proxyproto
|
||||
bash generate.sh
|
||||
cd -
|
||||
|
||||
cd internal/unigrpc/unistream
|
||||
bash generate.sh
|
||||
cd -
|
||||
207
misc/scripts/package.sh
Executable file
207
misc/scripts/package.sh
Executable file
@@ -0,0 +1,207 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Debian 7: lsb sysv
|
||||
# Debian 8: systemd
|
||||
# Ubuntu 14.04: upstart
|
||||
# Ubuntu 16.04: systemd
|
||||
# Centos 6: sysv
|
||||
# Centos 7: systemd
|
||||
#
|
||||
if [ "$1" = "" ]
|
||||
then
|
||||
echo "Usage: $0 <version> <iteration>"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ "$2" = "" ]
|
||||
then
|
||||
echo "Usage: $0 <version> <iteration>"
|
||||
exit
|
||||
fi
|
||||
|
||||
VERSION=$1
|
||||
ITERATION=$2
|
||||
UNIX_NOW=$( date +%s )
|
||||
|
||||
INSTALL_DIR=/usr/bin
|
||||
LOG_DIR=/var/log/centrifugo
|
||||
CONFIG_DIR=/etc/centrifugo
|
||||
LOGROTATE_DIR=/etc/logrotate.d
|
||||
DATA_DIR=/var/lib/centrifugo
|
||||
SCRIPT_DIR=/usr/lib/centrifugo
|
||||
|
||||
SAMPLE_CONFIGURATION=misc/packaging/config.json
|
||||
INITD_SCRIPT=misc/packaging/initd.sh
|
||||
INITD_EL6_SCRIPT=misc/packaging/initd.el6.sh
|
||||
UPSTART_SCRIPT=misc/packaging/centrifugo.upstart
|
||||
SYSTEMD_SCRIPT=misc/packaging/centrifugo.service
|
||||
POSTINSTALL_SCRIPT=misc/packaging/post_install.sh
|
||||
PREINSTALL_SCRIPT=misc/packaging/pre_install.sh
|
||||
POSTUNINSTALL_SCRIPT=misc/packaging/post_uninstall.sh
|
||||
LOGROTATE=misc/packaging/logrotate
|
||||
|
||||
TMP_WORK_DIR=`mktemp -d`
|
||||
TMP_BINARIES_DIR=`mktemp -d`
|
||||
ARCH=amd64
|
||||
NAME=centrifugo
|
||||
LICENSE=MIT
|
||||
URL="https://github.com/centrifugal/centrifugo"
|
||||
MAINTAINER="frvzmb@gmail.com"
|
||||
VENDOR=centrifugo
|
||||
DESCRIPTION="Real-time messaging server"
|
||||
|
||||
echo "Start packaging, version: $VERSION, iteration: $ITERATION, release time: $UNIX_NOW"
|
||||
|
||||
# cleanup_exit removes all resources created during the process and exits with
|
||||
# the supplied returned code.
|
||||
cleanup_exit() {
|
||||
rm -r $TMP_WORK_DIR
|
||||
rm -r $TMP_BINARIES_DIR
|
||||
exit $1
|
||||
}
|
||||
|
||||
# make_dir_tree creates the directory structure within the packages.
|
||||
make_dir_tree() {
|
||||
work_dir=$1
|
||||
|
||||
mkdir -p $work_dir/$INSTALL_DIR
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create install directory -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
mkdir -p $work_dir/$SCRIPT_DIR/scripts
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create script directory -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
mkdir -p $work_dir/$CONFIG_DIR
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create configuration directory -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
mkdir -p $work_dir/$LOGROTATE_DIR
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create logrotate directory -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# do_build builds the code. The version and commit must be passed in.
|
||||
do_build() {
|
||||
echo "Start building binary"
|
||||
|
||||
if [ -z "$STATS_ENDPOINT" ]
|
||||
then
|
||||
echo "STATS_ENDPOINT not defined"
|
||||
cleanup_exit 1
|
||||
else
|
||||
echo "STATS_ENDPOINT found"
|
||||
fi
|
||||
|
||||
if [ -z "$STATS_TOKEN" ]
|
||||
then
|
||||
echo "STATS_TOKEN not defined"
|
||||
cleanup_exit 1
|
||||
else
|
||||
echo "STATS_TOKEN found"
|
||||
fi
|
||||
|
||||
CGO_ENABLED=0 GOOS="linux" GOARCH="amd64" go build -ldflags="-X github.com/centrifugal/centrifugo/v6/internal/build.Version=$VERSION -X github.com/centrifugal/centrifugo/v6/internal/build.UsageStatsEndpoint=$STATS_ENDPOINT -X github.com/centrifugal/centrifugo/v6/internal/build.UsageStatsToken=$STATS_TOKEN" -o "$TMP_BINARIES_DIR/centrifugo"
|
||||
echo "Binary build completed successfully"
|
||||
"$TMP_BINARIES_DIR"/centrifugo version
|
||||
}
|
||||
|
||||
do_build $VERSION
|
||||
|
||||
make_dir_tree $TMP_WORK_DIR
|
||||
|
||||
cp $TMP_BINARIES_DIR/centrifugo $TMP_WORK_DIR/$INSTALL_DIR/
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to copy binaries to packaging directory ($TMP_WORK_DIR/$INSTALL_DIR/) -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
|
||||
echo "centrifugo binary copied to $TMP_WORK_DIR/$INSTALL_DIR/"
|
||||
|
||||
cp $INITD_SCRIPT $TMP_WORK_DIR/$SCRIPT_DIR/scripts/initd.sh
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to copy init.d script to packaging directory ($TMP_WORK_DIR/$SCRIPT_DIR/) -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
|
||||
echo "initd script copied to $TMP_WORK_DIR/$SCRIPT_DIR/scripts"
|
||||
|
||||
cp $INITD_EL6_SCRIPT $TMP_WORK_DIR/$SCRIPT_DIR/scripts/initd.el6.sh
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to copy initd.el6.sh script to packaging directory ($TMP_WORK_DIR/$SCRIPT_DIR/) -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
|
||||
echo "initd.el6 script copied to $TMP_WORK_DIR/$SCRIPT_DIR/scripts"
|
||||
|
||||
cp $SYSTEMD_SCRIPT $TMP_WORK_DIR/$SCRIPT_DIR/scripts/centrifugo.service
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to copy systemd script to packaging directory -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
|
||||
echo "systemd script copied to $TMP_WORK_DIR/$SCRIPT_DIR/scripts"
|
||||
|
||||
cp $UPSTART_SCRIPT $TMP_WORK_DIR/$SCRIPT_DIR/scripts/centrifugo.upstart
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to copy upstart script to packaging directory -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
|
||||
echo "upstart script copied to $TMP_WORK_DIR/$SCRIPT_DIR/scripts"
|
||||
|
||||
install -m 644 $LOGROTATE $TMP_WORK_DIR/$LOGROTATE_DIR/centrifugo
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to copy logrotate configuration to packaging directory -- aborting."
|
||||
cleanup_exit 1
|
||||
fi
|
||||
|
||||
echo "logrotate script copied to $TMP_WORK_DIR/$LOGROTATE_DIR/centrifugo"
|
||||
|
||||
COMMON_FPM_ARGS="\
|
||||
-C $TMP_WORK_DIR \
|
||||
--log error \
|
||||
--version $VERSION \
|
||||
--iteration $ITERATION \
|
||||
--name $NAME \
|
||||
--vendor $VENDOR \
|
||||
--url $URL \
|
||||
--category Network \
|
||||
--license $LICENSE \
|
||||
--maintainer $MAINTAINER \
|
||||
--force \
|
||||
--after-install $POSTINSTALL_SCRIPT \
|
||||
--before-install $PREINSTALL_SCRIPT \
|
||||
--after-remove $POSTUNINSTALL_SCRIPT \
|
||||
--config-files $CONFIG_DIR \
|
||||
--config-files $LOGROTATE_DIR "
|
||||
|
||||
echo "Start building rpm package"
|
||||
|
||||
rm -r ./PACKAGES
|
||||
mkdir -p PACKAGES
|
||||
|
||||
fpm -s dir -t rpm $COMMON_FPM_ARGS --description "$DESCRIPTION" \
|
||||
--rpm-os linux \
|
||||
-p PACKAGES/ \
|
||||
-a amd64 .
|
||||
|
||||
echo "Start building deb package"
|
||||
|
||||
fpm -s dir -t deb $COMMON_FPM_ARGS --description "$DESCRIPTION" \
|
||||
-p PACKAGES/ \
|
||||
-a amd64 .
|
||||
|
||||
cd PACKAGES
|
||||
for f in *.deb; do sha256sum $f >> ${f}_checksum.txt; done
|
||||
for f in *.rpm; do sha256sum $f >> ${f}_checksum.txt; done
|
||||
cd ..
|
||||
|
||||
echo "Packaging complete!"
|
||||
|
||||
cleanup_exit 0
|
||||
6
misc/scripts/release_package.sh
Executable file
6
misc/scripts/release_package.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
sudo gem install rake
|
||||
sudo gem install package_cloud
|
||||
sudo gem install fpm
|
||||
sudo apt-get install -y rpm
|
||||
make package
|
||||
2
misc/scripts/release_packagecloud.sh
Executable file
2
misc/scripts/release_packagecloud.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
make packagecloud
|
||||
56
misc/scripts/update-deps.sh
Executable file
56
misc/scripts/update-deps.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
# Update all Go module dependencies.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/update-deps.sh # minor/patch updates only
|
||||
# ./scripts/update-deps.sh --major # also apply major version updates
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(pwd)"
|
||||
|
||||
pwd
|
||||
|
||||
# Packages to exclude from automatic updates (updated manually).
|
||||
EXCLUDE=(
|
||||
"github.com/centrifugal/centrifuge"
|
||||
"github.com/mailru/easyjson"
|
||||
)
|
||||
|
||||
# Record current versions of excluded packages.
|
||||
SAVED=()
|
||||
for pkg in "${EXCLUDE[@]}"; do
|
||||
ver=$(grep "^[[:space:]]*${pkg} " go.mod | awk '{print $2}')
|
||||
if [[ -n "$ver" ]]; then
|
||||
SAVED+=("${pkg}@${ver}")
|
||||
fi
|
||||
done
|
||||
|
||||
echo "==> Updating all dependencies to latest minor/patch versions..."
|
||||
go get -u ./...
|
||||
|
||||
# Restore excluded packages to their original versions.
|
||||
for entry in "${SAVED[@]}"; do
|
||||
echo "==> Pinning $entry (excluded from update)"
|
||||
go get "$entry"
|
||||
done
|
||||
|
||||
echo "==> Running go mod tidy..."
|
||||
go mod tidy
|
||||
|
||||
if [[ "${1:-}" == "--major" ]]; then
|
||||
if ! command -v gomajor &>/dev/null; then
|
||||
echo "gomajor not found, installing..."
|
||||
go install github.com/icholy/gomajor@latest
|
||||
fi
|
||||
echo "==> Checking for major version updates..."
|
||||
gomajor list
|
||||
echo ""
|
||||
read -rp "Apply all major version updates? [y/N] " answer
|
||||
if [[ "$answer" =~ ^[Yy]$ ]]; then
|
||||
gomajor get -a ./...
|
||||
echo "==> Running go mod tidy after major updates..."
|
||||
go mod tidy
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "==> Done. Review changes with: git diff go.mod"
|
||||
25
misc/scripts/update_swagger_web.sh
Executable file
25
misc/scripts/update_swagger_web.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
# this script updates embedded swagger web interface.
|
||||
# Script intended to be run from the repo root folder:
|
||||
# ./misc/scripts/update_swagger_web.sh
|
||||
|
||||
TMP_WORK_DIR=$(mktemp -d)
|
||||
|
||||
# cleanup_exit removes all resources created during the process and exits with
|
||||
# the supplied returned code.
|
||||
cleanup_exit() {
|
||||
rm -rf "$TMP_WORK_DIR"
|
||||
exit "$1"
|
||||
}
|
||||
|
||||
git clone -c advice.detachedHead=false --depth 1 --branch v4.18.1 --single-branch https://github.com/swagger-api/swagger-ui.git "$TMP_WORK_DIR"
|
||||
cp internal/apiproto/swagger/api.swagger.json "$TMP_WORK_DIR"/dist/swagger.json
|
||||
|
||||
oldString="https://petstore.swagger.io/v2/"
|
||||
newString="./"
|
||||
sed -i '' "s|${oldString}|${newString}|g" "$TMP_WORK_DIR"/dist/swagger-initializer.js
|
||||
|
||||
statik -src="$TMP_WORK_DIR"/dist -dest ./internal/ -package=swaggerui
|
||||
|
||||
cleanup_exit 0
|
||||
20
misc/scripts/update_web.sh
Executable file
20
misc/scripts/update_web.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# this script updates embedded web interface.
|
||||
# Script intended to be run from the repo root folder:
|
||||
# ./misc/scripts/update_web.sh
|
||||
|
||||
TMP_WORK_DIR=$(mktemp -d)
|
||||
|
||||
# cleanup_exit removes all resources created during the process and exits with
|
||||
# the supplied returned code.
|
||||
cleanup_exit() {
|
||||
rm -rf "$TMP_WORK_DIR"
|
||||
exit "$1"
|
||||
}
|
||||
|
||||
git clone -c advice.detachedHead=false --depth 1 --branch main --single-branch https://github.com/centrifugal/web.git "$TMP_WORK_DIR"
|
||||
|
||||
statik -src="$TMP_WORK_DIR"/dist -dest ./internal/ -package=webui
|
||||
|
||||
cleanup_exit 0
|
||||
7
misc/wrk/api_history.lua
Normal file
7
misc/wrk/api_history.lua
Normal file
@@ -0,0 +1,7 @@
|
||||
-- CENTRIFUGO_HISTORY_SIZE=100 CENTRIFUGO_HISTORY_TTL=3600s ./centrifugo --api_insecure
|
||||
-- for i in {1..100}; do curl -X POST http://localhost:8000/api --data '{"method": "publish", "params": {"channel": "index", "data": "hello"}}'; done
|
||||
-- curl -X POST http://localhost:8000/api --data '{"method": "history", "params": {"channel": "index", "limit": -1}}' | python3 -m json.tool
|
||||
-- wrk -s api_history.lua http://localhost:8000/api -d 2s -t 5 -c 40
|
||||
wrk.method = "POST"
|
||||
wrk.body = '{"method": "history", "params": {"channel": "index", "limit": -1}}'
|
||||
wrk.headers["Content-Type"] = "application/json"
|
||||
5
misc/wrk/api_publish.lua
Normal file
5
misc/wrk/api_publish.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
-- ./centrifugo --api_insecure
|
||||
-- wrk -s api_publish.lua http://localhost:8000/api -d 2s -t 5 -c 40
|
||||
wrk.method = "POST"
|
||||
wrk.body = '{"method": "publish", "params": {"channel": "index", "data": 1}}'
|
||||
wrk.headers["Content-Type"] = "application/json"
|
||||
Reference in New Issue
Block a user