infra/chat.k8s.yml
2025-06-10 14:35:12 +02:00

544 lines
14 KiB
YAML

apiVersion: v1
kind: Namespace
metadata:
name: chat
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: database
namespace: chat
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: zfs-nfs
---
kind: ConfigMap
apiVersion: v1
metadata:
name: database-config
namespace: chat
data:
POSTGRES_USER: admin
POSTGRES_PASSWORD: admin
POSTGRES_DB: chat
PGDATA: /var/lib/postgresql/data/pgdata
---
kind: ConfigMap
apiVersion: v1
metadata:
name: database-init
namespace: chat
data:
10-init.sql: |-
create table if not exists "user" (
"id" serial primary key,
"username" varchar(32) not null unique,
"password_hash" varchar(72) not null,
"status" varchar(200) not null default '',
"picture" varchar(300) not null default '',
"bio" varchar(200) not null default ''
);
create table if not exists "role" (
"id" serial primary key,
"name" varchar(32) not null unique
);
create table if not exists "role_binding" (
"user_id" integer not null references "user"("id") on delete cascade,
"role_id" integer not null references "role"("id") on delete cascade,
primary key ("user_id", "role_id"),
unique ("user_id", "role_id")
);
create table if not exists "channel" (
"id" serial primary key,
"name" varchar(32) not null unique,
"description" varchar(200) default null
);
create type "right_t" as enum ('R', 'W', 'RW', 'A');
create table if not exists "right" (
"role_id" integer not null references "role"("id"),
"channel_id" integer not null references "channel"("id"),
"rights" right_t not null,
primary key ("role_id", "channel_id"),
unique ("role_id", "channel_id")
);
create table if not exists "message" (
"id" serial primary key,
"sender_id" integer not null references "user"("id") on delete cascade,
"channel_id" integer not null references "channel"("id") on delete cascade,
"time" timestamp without time zone default (now() at time zone 'utc'),
"content" varchar(2000) not null
);
20-procedures.sql: |-
create or replace procedure add_user(
in in_username varchar(32),
in in_password_hash varchar(72)
)
language plpgsql
as $$
begin
insert into "user" ("username", "password_hash")
values (in_username, in_password_hash);
insert into "role_binding" ("user_id", "role_id")
values (
(
select "id"
from "user"
where "username" = in_username
),
(
select "id"
from "role"
where "name" = 'member'
)
);
end;
$$;
create or replace procedure add_channel(
in channel_name varchar(32),
in channel_desc varchar(200)
)
language plpgsql
as $$
declare
channel_id integer;
begin
insert into "channel" ("name", "description")
values (channel_name, channel_desc)
returning "id" into channel_id;
insert into "right" ("role_id", "channel_id", "rights")
values (
(
select "id"
from "role"
where "name" = 'admin'
),
channel_id,
'A'
);
end;
$$
30-triggers.sql: |-
-- prevent deleting or changing default roles
create rule protect_special_roles_update as
on update to "role"
where old."name" = 'admin' or old."name" = 'member'
do instead nothing;
create rule protect_special_roles_delete as
on delete to "role"
where old."name" = 'admin' or old."name" = 'member'
do instead nothing;
40-views.sql: |-
create or replace view "user_rigths_per_channel" as
select distinct
"u"."id" as "user_id",
"c"."id" as "channel_id",
"c"."name" as "channel_name",
"c"."description" as "channel_description",
"r"."rights"
from
"user" "u"
join
"role_binding" "rb" on "u"."id" = "rb"."user_id"
join
"role" "ro" on "rb"."role_id" = "ro"."id"
join
"right" "r" on "ro"."id" = "r"."role_id"
join
"channel" "c" on "r"."channel_id" = "c"."id"
order by "user_id";
50-default.sql: |-
-- admin account and its role and right
insert into "user" ("username", "password_hash")
values (
'admin',
'$2a$12$FChbwNEIH9imtkTAkNq35eqMb.1C.1BP3bbuFZwOr7rOrs5luwCzq'
);
insert into "role" ("name")
values ('admin');
insert into "role_binding" ("user_id", "role_id")
values (
(
select "id"
from "user"
where "username" = 'admin'
),
(
select "id"
from "role"
where "name" = 'admin'
)
);
-- default channel
call add_channel('default', 'default channel');
-- member role
insert into "role" ("name")
values ('member');
insert into "right" ("role_id", "channel_id", "rights")
values (
(
select "id"
from "role"
where "name" = 'member'
),
(
select "id"
from "channel"
where "name" = 'default'
),
'RW'
);
99-sample.sql: |-
-- More users
insert into "user" ("username", "password_hash", "status", "picture", "bio")
values
('alice', '$2a$12$FChbwNEIH9imtkTAkNq35eqMb.1C.1BP3bbuFZwOr7rOrs5luwCzq', 'Online', '', 'Loves coding and coffee.'),
('bob', '$2a$12$FChbwNEIH9imtkTAkNq35eqMb.1C.1BP3bbuFZwOr7rOrs5luwCzq', 'Away', '', 'Gamer and tech enthusiast.'),
('charlie', '$2a$12$FChbwNEIH9imtkTAkNq35eqMb.1C.1BP3bbuFZwOr7rOrs5luwCzq', 'Busy', '', 'Database aficionado.'),
('diana', '$2a$12$FChbwNEIH9imtkTAkNq35eqMb.1C.1BP3bbuFZwOr7rOrs5luwCzq', 'Online', '', 'Enjoys reading and hiking.');
-- More roles
insert into "role" ("name")
values
('moderator'),
('guest');
-- More channels
call add_channel('general', 'General discussion channel');
call add_channel('development', 'Channel for development topics');
call add_channel('random', 'Anything goes in here');
-- Role bindings for new users
insert into "role_binding" ("user_id", "role_id")
values
((select "id" from "user" where "username" = 'alice'), (select "id" from "role" where "name" = 'member')),
((select "id" from "user" where "username" = 'bob'), (select "id" from "role" where "name" = 'member')),
((select "id" from "user" where "username" = 'charlie'), (select "id" from "role" where "name" = 'moderator')),
((select "id" from "user" where "username" = 'diana'), (select "id" from "role" where "name" = 'guest'));
-- Rights for new roles and channels
insert into "right" ("role_id", "channel_id", "rights")
values
((select "id" from "role" where "name" = 'moderator'), (select "id" from "channel" where "name" = 'general'), 'RW'),
((select "id" from "role" where "name" = 'moderator'), (select "id" from "channel" where "name" = 'development'), 'RW'),
((select "id" from "role" where "name" = 'guest'), (select "id" from "channel" where "name" = 'general'), 'R'),
((select "id" from "role" where "name" = 'member'), (select "id" from "channel" where "name" = 'general'), 'RW'),
((select "id" from "role" where "name" = 'member'), (select "id" from "channel" where "name" = 'development'), 'RW'),
((select "id" from "role" where "name" = 'member'), (select "id" from "channel" where "name" = 'random'), 'RW');
-- Sample messages
insert into "message" ("sender_id", "channel_id", "content")
values
((select "id" from "user" where "username" = 'alice'), (select "id" from "channel" where "name" = 'general'), 'Hey everyone, glad to be here!'),
((select "id" from "user" where "username" = 'bob'), (select "id" from "channel" where "name" = 'default'), 'Good morning!'),
((select "id" from "user" where "username" = 'charlie'), (select "id" from "channel" where "name" = 'development'), 'Working on a new feature, updates coming soon.'),
((select "id" from "user" where "username" = 'alice'), (select "id" from "channel" where "name" = 'general'), 'Does anyone have experience with PostgreSQL?'),
((select "id" from "user" where "username" = 'diana'), (select "id" from "channel" where "name" = 'general'), 'Hello! Just joined the server.'),
((select "id" from "user" where "username" = 'bob'), (select "id" from "channel" where "name" = 'random'), 'Anyone up for some gaming later?'),
((select "id" from "user" where "username" = 'admin'), (select "id" from "channel" where "name" = 'default'), 'Welcome all new users!');
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: database
namespace: chat
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
spec:
containers:
- name: database
image: postgres:17-alpine
ports:
- containerPort: 5432
protocol: TCP
envFrom:
- configMapRef:
name: database-config
resources:
requests:
cpu: "10m"
memory: "10Mi"
limits:
cpu: "2000m"
memory: "1024Mi"
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: database
- mountPath: /docker-entrypoint-initdb.d
name: database-init
volumes:
- name: database
persistentVolumeClaim:
claimName: database
- name: database-init
configMap:
name: database-init
---
kind: Service
apiVersion: v1
metadata:
name: database
namespace: chat
spec:
ports:
- port: 5432
targetPort: 5432
selector:
app: database
---
kind: ConfigMap
apiVersion: v1
metadata:
name: keystore-config
namespace: chat
data:
valkey.conf: |-
port 6379
databases 1
aclfile /usr/local/etc/valkey/users.acl
users.acl: |-
user default off
user admin on >admin allcommands allkeys &*
user readonly on >readonly ~* +@read &*
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: keystore
namespace: chat
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: keystore
template:
metadata:
labels:
app: keystore
spec:
containers:
- name: keystore
image: valkey/valkey:8-alpine
ports:
- containerPort: 6379
protocol: TCP
command: ["valkey-server"]
args: ["/usr/local/etc/valkey/valkey.conf"]
resources:
requests:
cpu: "10m"
memory: "10Mi"
limits:
cpu: "2000m"
memory: "1024Mi"
volumeMounts:
- mountPath: /usr/local/etc/valkey/valkey.conf
name: keystore-config
subPath: valkey.conf
- mountPath: /usr/local/etc/valkey/users.acl
name: keystore-config
subPath: users.acl
volumes:
- name: keystore-config
configMap:
name: keystore-config
---
kind: Service
apiVersion: v1
metadata:
name: keystore
namespace: chat
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: keystore
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: ui
namespace: chat
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: ui
template:
metadata:
labels:
app: ui
spec:
containers:
- name: ui
image: registry.tek.govt.hu/chat/ui:debug
ports:
- containerPort: 80
protocol: TCP
resources:
requests:
cpu: "10m"
memory: "10Mi"
limits:
cpu: "2000m"
memory: "1024Mi"
---
kind: Service
apiVersion: v1
metadata:
name: ui
namespace: chat
spec:
ports:
- port: 80
targetPort: 80
selector:
app: ui
---
kind: ConfigMap
apiVersion: v1
metadata:
name: api-config
namespace: chat
data:
API_ADDRESS: :5000
API_BASE: api
API_TOKEN_LIFE: "600"
DB_HOST: database
DB_PORT: "5432"
DB_USER: admin
DB_PASSWORD: admin
DB_NAME: chat
VALKEY_ADDRESS: keystore:6379
VALKEY_USERNAME: admin
VALKEY_PASSWORD: admin
VALKEY_DB: "0"
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: api
namespace: chat
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: registry.tek.govt.hu/chat/api:latest
ports:
- containerPort: 5000
protocol: TCP
envFrom:
- configMapRef:
name: api-config
resources:
requests:
cpu: "10m"
memory: "10Mi"
limits:
cpu: "2000m"
memory: "1024Mi"
---
kind: Service
apiVersion: v1
metadata:
name: api
namespace: chat
spec:
ports:
- port: 5000
targetPort: 5000
selector:
app: api
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
namespace: chat
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
traefik.ingress.kubernetes.io/router.entrypoints: web, websecure
traefik.ingress.kubernetes.io/router.middlewares: >-
traefik-secure@kubernetescrd,traefik-ws@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- chat.tek.govt.hu
secretName: chat-tls
rules:
- host: chat.tek.govt.hu
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 5000
- path: /
pathType: Prefix
backend:
service:
name: ui
port:
number: 80