Adds indico app.

This commit is contained in:
2026-06-19 21:33:05 +00:00
parent 761712ba71
commit f9ef89f00d
12 changed files with 568 additions and 0 deletions

6
indico/app.yaml Normal file
View File

@@ -0,0 +1,6 @@
name: indico
is: indico
description: Indico is an open-source conference and event management system developed at CERN. It handles the full lifecycle of events from abstract submission to proceedings publication.
icon: https://raw.githubusercontent.com/indico/indico/master/indico/web/static/images/logo_indico.png
category: productivity
latest: "3"

View File

@@ -0,0 +1,43 @@
# Indico
Indico is a feature-rich open-source conference and event management system, developed by CERN.
## Dependencies
- **PostgreSQL** - Database for storing events, registrations, and abstracts
- **Redis** - Used for caching and background job queuing
- **SMTP** - For sending registration confirmations and notifications
## Configuration
Key settings in `config.yaml`:
- **domain** - Where Indico will be accessible
- **storage** - Persistent volume size for uploaded files (default: `2Gi`)
- **timezone** - Default timezone for events (default: `UTC`)
- **smtp** - Email settings inherited from your Wild Cloud SMTP service
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add indico
wild app deploy indico
```
2. Create the admin user via the Indico shell:
```bash
kubectl exec -n indico deploy/indico -- indico shell -c \
"from indico.modules.users.operations import create_user; \
from indico.core.db import db; \
u = create_user('admin@example.com', {'first_name': 'Admin', 'last_name': 'User', 'affiliation': ''}, is_admin=True); \
db.session.commit(); print('Done')"
```
3. Log in at the app URL with the admin account and set a password via **Profile → Change Password**.
## Notes
- Indico is built for scientific and academic conferences — it supports abstracts, paper reviews, registration forms, and room booking
- The `secretKey` is auto-generated in `secrets.yaml`
- Categories are the top-level containers; events and conferences belong to categories

View File

@@ -0,0 +1,44 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: indico-config
namespace: {{ .namespace }}
data:
indico.conf.tpl: |
import os
# Database - credentials injected via environment variables
SQLALCHEMY_DATABASE_URI = f'postgresql://{os.environ["PGUSER"]}:{os.environ["PGPASSWORD"]}@{os.environ["PGHOST"]}:{os.environ["PGPORT"]}/{os.environ["PGDATABASE"]}'
del os
SECRET_KEY = 'SECRET_KEY_PLACEHOLDER'
BASE_URL = 'https://{{ .domain }}'
USE_PROXY = True
DEFAULT_TIMEZONE = '{{ .timezone }}'
DEFAULT_LOCALE = 'en_GB'
REDIS_CACHE_URL = 'redis://:REDIS_PASSWORD_PLACEHOLDER@{{ .redis.host }}:{{ .redis.port }}/0'
CELERY_BROKER = 'redis://:REDIS_PASSWORD_PLACEHOLDER@{{ .redis.host }}:{{ .redis.port }}/1'
NO_REPLY_EMAIL = '{{ .smtp.from }}'
SUPPORT_EMAIL = '{{ .smtp.from }}'
SMTP_SERVER = '{{ .smtp.host }}'
SMTP_PORT = {{ .smtp.port }}
SMTP_USE_TLS = False
SMTP_LOGIN = '{{ .smtp.user }}'
SMTP_PASSWORD = 'SMTP_PASSWORD_PLACEHOLDER'
LOG_DIR = '/opt/indico/log'
TEMP_DIR = '/opt/indico/tmp'
CACHE_DIR = '/opt/indico/cache'
CUSTOMIZATION_DIR = '/opt/indico/custom'
STORAGE_BACKENDS = {'default': 'fs:/opt/indico/archive'}
ATTACHMENT_STORAGE = 'default'
ENABLE_ROOMBOOKING = True
PLUGINS = {'previewer_code'}

View File

@@ -0,0 +1,68 @@
apiVersion: batch/v1
kind: Job
metadata:
name: indico-db-init
namespace: {{ .namespace }}
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
restartPolicy: OnFailure
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: db-init
image: postgres:17
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
command: ["/bin/bash", "-c"]
args:
- |
PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOST} -U postgres <<EOF
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${DB_USER}') THEN
CREATE USER ${DB_USER} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
ELSE
ALTER USER ${DB_USER} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
END IF;
END
\$\$;
SELECT 'CREATE DATABASE ${DB_NAME}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_NAME}')\gexec
ALTER DATABASE ${DB_NAME} OWNER TO ${DB_USER};
GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};
EOF
PGPASSWORD=${DB_PASSWORD} psql -h ${DB_HOST} -U ${DB_USER} -d ${DB_NAME} <<EOF
CREATE EXTENSION IF NOT EXISTS unaccent;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
EOF
env:
- name: POSTGRES_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: password
- name: DB_HOST
value: "{{ .db.host }}"
- name: DB_NAME
value: "{{ .db.name }}"
- name: DB_USER
value: "{{ .db.user }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: indico-secrets
key: dbPassword

View File

@@ -0,0 +1,160 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: indico-worker
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: worker
template:
metadata:
labels:
component: worker
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
seccompProfile:
type: RuntimeDefault
initContainers:
- name: config-init
image: getindico/indico:3.3.12
command: ["/bin/sh", "-c"]
args:
- |
sed \
-e "s|SECRET_KEY_PLACEHOLDER|${SECRET_KEY}|g" \
-e "s|REDIS_PASSWORD_PLACEHOLDER|${REDIS_PASSWORD}|g" \
-e "s|SMTP_PASSWORD_PLACEHOLDER|${SMTP_PASSWORD}|g" \
/etc/indico-base/indico.conf.tpl > /opt/indico/etc/indico.conf
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
env:
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: indico-secrets
key: secretKey
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: indico-secrets
key: redis.password
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: indico-secrets
key: smtp.password
volumeMounts:
- name: indico-config-base
mountPath: /etc/indico-base
- name: indico-config
mountPath: /opt/indico/etc
containers:
- name: worker
image: getindico/indico:3.3.12
args: ["/opt/indico/run_celery.sh"]
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
resources:
requests:
memory: 256Mi
cpu: 50m
limits:
memory: 2Gi
cpu: "1"
env:
- name: PGHOST
value: "{{ .db.host }}"
- name: PGPORT
value: "{{ .db.port }}"
- name: PGUSER
value: "{{ .db.user }}"
- name: PGDATABASE
value: "{{ .db.name }}"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: indico-secrets
key: dbPassword
volumeMounts:
- name: indico-config
mountPath: /opt/indico/etc
- name: indico-archive
mountPath: /opt/indico/archive
- name: indico-tmp
mountPath: /opt/indico/tmp
- name: indico-log
mountPath: /opt/indico/log
- name: indico-cache
mountPath: /opt/indico/cache
- name: indico-custom
mountPath: /opt/indico/custom
- name: beat
image: getindico/indico:3.3.12
args: ["/opt/indico/run_celery.sh", "beat"]
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
resources:
requests:
memory: 256Mi
cpu: 50m
limits:
memory: 1Gi
cpu: 200m
env:
- name: PGHOST
value: "{{ .db.host }}"
- name: PGPORT
value: "{{ .db.port }}"
- name: PGUSER
value: "{{ .db.user }}"
- name: PGDATABASE
value: "{{ .db.name }}"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: indico-secrets
key: dbPassword
volumeMounts:
- name: indico-config
mountPath: /opt/indico/etc
- name: indico-tmp
mountPath: /opt/indico/tmp
- name: indico-log
mountPath: /opt/indico/log
- name: indico-cache
mountPath: /opt/indico/cache
volumes:
- name: indico-config-base
configMap:
name: indico-config
- name: indico-config
emptyDir: {}
- name: indico-archive
persistentVolumeClaim:
claimName: indico-archive
- name: indico-tmp
emptyDir:
medium: Memory
- name: indico-log
emptyDir: {}
- name: indico-cache
emptyDir: {}
- name: indico-custom
emptyDir: {}

View File

@@ -0,0 +1,142 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: indico
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
seccompProfile:
type: RuntimeDefault
initContainers:
- name: config-init
image: getindico/indico:3.3.12
command: ["/bin/sh", "-c"]
args:
- |
sed \
-e "s|SECRET_KEY_PLACEHOLDER|${SECRET_KEY}|g" \
-e "s|REDIS_PASSWORD_PLACEHOLDER|${REDIS_PASSWORD}|g" \
-e "s|SMTP_PASSWORD_PLACEHOLDER|${SMTP_PASSWORD}|g" \
/etc/indico-base/indico.conf.tpl > /opt/indico/etc/indico.conf
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
env:
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: indico-secrets
key: secretKey
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: indico-secrets
key: redis.password
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: indico-secrets
key: smtp.password
volumeMounts:
- name: indico-config-base
mountPath: /etc/indico-base
- name: indico-config
mountPath: /opt/indico/etc
containers:
- name: indico
image: getindico/indico:3.3.12
args: ["/opt/indico/run_indico.sh"]
ports:
- name: http
containerPort: 59999
protocol: TCP
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
resources:
requests:
memory: 256Mi
cpu: 50m
limits:
memory: 2Gi
cpu: "2"
env:
- name: PGHOST
value: "{{ .db.host }}"
- name: PGPORT
value: "{{ .db.port }}"
- name: PGUSER
value: "{{ .db.user }}"
- name: PGDATABASE
value: "{{ .db.name }}"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: indico-secrets
key: dbPassword
livenessProbe:
tcpSocket:
port: 59999
initialDelaySeconds: 120
periodSeconds: 30
failureThreshold: 6
readinessProbe:
tcpSocket:
port: 59999
initialDelaySeconds: 60
periodSeconds: 15
failureThreshold: 3
volumeMounts:
- name: indico-config
mountPath: /opt/indico/etc
- name: indico-archive
mountPath: /opt/indico/archive
- name: indico-tmp
mountPath: /opt/indico/tmp
- name: indico-log
mountPath: /opt/indico/log
- name: indico-cache
mountPath: /opt/indico/cache
- name: indico-custom
mountPath: /opt/indico/custom
- name: indico-static
mountPath: /opt/indico/static-shared
volumes:
- name: indico-config-base
configMap:
name: indico-config
- name: indico-config
emptyDir: {}
- name: indico-archive
persistentVolumeClaim:
claimName: indico-archive
- name: indico-tmp
emptyDir:
medium: Memory
- name: indico-log
emptyDir: {}
- name: indico-cache
emptyDir: {}
- name: indico-custom
emptyDir: {}
- name: indico-static
emptyDir: {}

View File

@@ -0,0 +1,26 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: indico
namespace: {{ .namespace }}
annotations:
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
external-dns.alpha.kubernetes.io/ttl: "60"
spec:
ingressClassName: traefik
rules:
- host: {{ .domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: indico
port:
number: 80
tls:
- hosts:
- {{ .domain }}
secretName: {{ .tlsSecretName }}

View File

@@ -0,0 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: {{ .namespace }}
labels:
- includeSelectors: true
pairs:
app: indico
managedBy: kustomize
partOf: wild-cloud
resources:
- namespace.yaml
- configmap.yaml
- pvc.yaml
- db-init-job.yaml
- deployment.yaml
- deployment-worker.yaml
- service.yaml
- ingress.yaml

View File

@@ -0,0 +1,32 @@
version: 3.3.12-1
requires:
- name: postgres
- name: redis
- name: smtp
defaultConfig:
namespace: indico
externalDnsDomain: "{{ .cloud.domain }}"
domain: "indico.{{ .cloud.domain }}"
tlsSecretName: wildcard-wild-cloud-tls
storage: 2Gi
timezone: UTC
db:
host: "{{ .apps.postgres.host }}"
port: "{{ .apps.postgres.port }}"
name: indico
user: indico
redis:
host: "{{ .apps.redis.host }}"
port: "6379"
smtp:
host: "{{ .apps.smtp.host }}"
port: "{{ .apps.smtp.port }}"
from: "{{ .apps.smtp.from }}"
user: "{{ .apps.smtp.user }}"
defaultSecrets:
- key: dbPassword
- key: secretKey
requiredSecrets:
- postgres.password
- redis.password
- smtp.password

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .namespace }}

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: indico-archive
namespace: {{ .namespace }}
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs
resources:
requests:
storage: {{ .storage }}

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: indico
namespace: {{ .namespace }}
spec:
selector:
component: web
ports:
- name: http
port: 80
targetPort: 59999
protocol: TCP