Backup 30.12.2024
This commit is contained in:
parent
fd0fb334c9
commit
2544c630b6
582 changed files with 198266 additions and 0 deletions
9
docker/README.md
Normal file
9
docker/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Don't forget to put the container checker into the crontab:
|
||||||
|
|
||||||
|
```
|
||||||
|
>> crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
*/5 * * * * /bin/bash /docker/check_docker.sh
|
||||||
|
```
|
11
docker/backup/README.md
Normal file
11
docker/backup/README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
In copy_keys_over.sh and make_keys.sh, you need to change the computer names to your installation (where your want to store your backup).
|
||||||
|
|
||||||
|
Don't forget to put it in your crontab
|
||||||
|
|
||||||
|
```
|
||||||
|
>> crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
0 0 * * * /bin/bash /docker/backup/make_backup.sh
|
||||||
|
```
|
1
docker/backup/copy_keys_over.sh
Normal file
1
docker/backup/copy_keys_over.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
scp backup.pub overleaf@backup.zfn.uni-bremen.de:~/.ssh/authorized_keys
|
12
docker/backup/make_backup.sh
Normal file
12
docker/backup/make_backup.sh
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
cd /docker/compose/keycloakpostgres
|
||||||
|
sh backup.sh
|
||||||
|
|
||||||
|
cd /docker/compose/overleafmongo
|
||||||
|
sh backup.sh
|
||||||
|
|
||||||
|
cd /docker/compose/overleafredis
|
||||||
|
sh backup.sh
|
||||||
|
|
||||||
|
cd /docker/backup/
|
||||||
|
rsync -avz --delete -e "ssh -i /docker/backup/backup" /docker overleaf@backup.zfn.uni-bremen.de:/home/overleaf/fb1/
|
1
docker/backup/make_keys.sh
Normal file
1
docker/backup/make_keys.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ssh-keygen -t ed25519 -f backup
|
15
docker/check_docker.sh
Normal file
15
docker/check_docker.sh
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# List of expected container names
|
||||||
|
expected_containers=("overleafregister" "nginx" "checkuser" "overleafserver" "keycloakserver" "keycloakpostgres" "overleafmongo" "overleafredis" "hajtexsshd")
|
||||||
|
|
||||||
|
# Email settings
|
||||||
|
recipient="overleaf@uni-bremen.de"
|
||||||
|
subject="Docker Container Alert"
|
||||||
|
|
||||||
|
# Check containers
|
||||||
|
for container in "${expected_containers[@]}"; do
|
||||||
|
if ! docker ps --format '{{.Names}}' | grep -q "^$container$"; then
|
||||||
|
echo "Container $container is not running" | mail -s "$subject" "$recipient"
|
||||||
|
fi
|
||||||
|
done
|
7
docker/compose/README.md
Normal file
7
docker/compose/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
The landing page of the HajTex server is
|
||||||
|
|
||||||
|
https://[FQDN]/launchpad
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
https://psintern.neuro.uni-bremen.de/launchpad
|
25
docker/compose/check_users/Dockerfile
Normal file
25
docker/compose/check_users/Dockerfile
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
FROM python:3.12.5
|
||||||
|
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt -y install mc
|
||||||
|
RUN apt -y install docker.io
|
||||||
|
RUN pip install pymongo
|
||||||
|
RUN pip install email_validator
|
||||||
|
RUN pip install flask
|
||||||
|
RUN pip install gunicorn
|
||||||
|
RUN pip install requests
|
||||||
|
RUN pip install BeautifulSoup4
|
||||||
|
RUN apt -y install bash
|
||||||
|
RUN pip install --upgrade pip
|
||||||
|
RUN pip install flask_wtf
|
||||||
|
RUN pip install wtforms
|
||||||
|
RUN pip install flask_recaptcha
|
||||||
|
RUN pip install Markup
|
||||||
|
RUN pip install captcha Pillow
|
||||||
|
RUN pip install argh
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/bash", "-c", "cd / && sleep infinity"]
|
||||||
|
|
||||||
|
|
21
docker/compose/check_users/compose.yaml
Normal file
21
docker/compose/check_users/compose.yaml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
services:
|
||||||
|
checkuser:
|
||||||
|
image: "check_user_image"
|
||||||
|
container_name: checkuser
|
||||||
|
hostname: checkuser
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- overleaf-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- /docker/compose/check_users/data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
entrypoint: >
|
||||||
|
/bin/sh -c "pip install argh && cd / && sleep infinity"
|
||||||
|
|
||||||
|
|
||||||
|
networks:
|
||||||
|
overleaf-network:
|
||||||
|
external: true
|
15
docker/compose/check_users/data/list_invited.py
Normal file
15
docker/compose/check_users/data/list_invited.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
container_name: str = "overleafmongo"
|
||||||
|
port: int = 27017
|
||||||
|
|
||||||
|
client = pymongo.MongoClient(container_name, port)
|
||||||
|
db = client.sharelatex
|
||||||
|
users = db.projectInvites
|
||||||
|
|
||||||
|
cursor = users.find()
|
||||||
|
|
||||||
|
for user in cursor:
|
||||||
|
print(user["email"])
|
||||||
|
|
||||||
|
client.close()
|
15
docker/compose/check_users/data/list_user.py
Normal file
15
docker/compose/check_users/data/list_user.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
container_name: str = "overleafmongo"
|
||||||
|
port: int = 27017
|
||||||
|
|
||||||
|
client = pymongo.MongoClient(container_name, port)
|
||||||
|
db = client.sharelatex
|
||||||
|
users = db.users
|
||||||
|
|
||||||
|
cursor = users.find()
|
||||||
|
|
||||||
|
for user in cursor:
|
||||||
|
print(user["email"])
|
||||||
|
|
||||||
|
client.close()
|
27
docker/compose/check_users/data/make_admin.py
Normal file
27
docker/compose/check_users/data/make_admin.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import pymongo
|
||||||
|
import argh
|
||||||
|
|
||||||
|
|
||||||
|
def main(
|
||||||
|
email_to_find: str, container_name: str = "overleafmongo", port: int = 27017
|
||||||
|
) -> bool:
|
||||||
|
print(f"User name: {email_to_find}")
|
||||||
|
client = pymongo.MongoClient(container_name, port)
|
||||||
|
db = client.sharelatex
|
||||||
|
users = db.users
|
||||||
|
search_result = users.find_one({"email": email_to_find})
|
||||||
|
|
||||||
|
if search_result is None:
|
||||||
|
print("User not found")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print(f"User status was: {search_result['isAdmin']}")
|
||||||
|
users.update_one({"email": email_to_find}, {"$set": {"isAdmin": True}})
|
||||||
|
print("User status changed")
|
||||||
|
return
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
argh.dispatch_command(main)
|
9
docker/compose/check_users/delete_user.sh
Normal file
9
docker/compose/check_users/delete_user.sh
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Error: Email address not provided"
|
||||||
|
echo "Usage: $0 <email_address>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker exec overleafserver /bin/bash -ce "cd /overleaf/services/web && node modules/server-ce-scripts/scripts/delete-user --email=$1"
|
1
docker/compose/check_users/down.sh
Normal file
1
docker/compose/check_users/down.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker compose down
|
1
docker/compose/check_users/exec.sh
Normal file
1
docker/compose/check_users/exec.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it checkuser bash
|
1
docker/compose/check_users/exec_list_invited.sh
Normal file
1
docker/compose/check_users/exec_list_invited.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it checkuser bash -c "cd /data ; python list_invited.py"
|
1
docker/compose/check_users/exec_list_user.sh
Normal file
1
docker/compose/check_users/exec_list_user.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it checkuser bash -c "cd /data ; python list_user.py"
|
1
docker/compose/check_users/exec_make_admin.sh
Normal file
1
docker/compose/check_users/exec_make_admin.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it checkuser bash -c "cd /data ; python make_admin.py $1"
|
1
docker/compose/check_users/logs.sh
Normal file
1
docker/compose/check_users/logs.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker compose logs -f
|
1
docker/compose/check_users/make_image.sh
Normal file
1
docker/compose/check_users/make_image.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker build --network host -t check_user_image .
|
1
docker/compose/check_users/up.sh
Normal file
1
docker/compose/check_users/up.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker compose up -d
|
1
docker/compose/inspect_texlive.sh
Normal file
1
docker/compose/inspect_texlive.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker run -it texlive/texlive:latest-full /bin/bash
|
3
docker/compose/keycloakpostgres/.env
Normal file
3
docker/compose/keycloakpostgres/.env
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
POSTGRES_DB=keycloak
|
||||||
|
POSTGRES_USER=keycloakuser
|
||||||
|
POSTGRES_PASSWORD=REDACTED
|
1
docker/compose/keycloakpostgres/backup.sh
Normal file
1
docker/compose/keycloakpostgres/backup.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec keycloakpostgres bash -c "pg_dump -U keycloakuser -d keycloak -F c -f /backup/backup.sql"
|
19
docker/compose/keycloakpostgres/compose.yaml
Normal file
19
docker/compose/keycloakpostgres/compose.yaml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: keycloakpostgres
|
||||||
|
hostname: keycloakpostgres
|
||||||
|
volumes:
|
||||||
|
- /docker/compose/keycloakpostgres/postgres_data:/var/lib/postgresql/data
|
||||||
|
- /docker/compose/keycloakpostgres/backup:/backup
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- keycloak-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
keycloak-network:
|
||||||
|
external: true
|
||||||
|
|
2
docker/compose/keycloakpostgres/down.sh
Normal file
2
docker/compose/keycloakpostgres/down.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose down
|
||||||
|
|
1
docker/compose/keycloakpostgres/exec.sh
Normal file
1
docker/compose/keycloakpostgres/exec.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it keycloakpostgres bash
|
2
docker/compose/keycloakpostgres/logs.sh
Normal file
2
docker/compose/keycloakpostgres/logs.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose logs -f
|
||||||
|
|
2
docker/compose/keycloakpostgres/up.sh
Normal file
2
docker/compose/keycloakpostgres/up.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose up -d
|
||||||
|
|
6
docker/compose/keycloakserver/.env
Normal file
6
docker/compose/keycloakserver/.env
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
POSTGRES_DB=keycloak
|
||||||
|
POSTGRES_USER=keycloakuser
|
||||||
|
POSTGRES_PASSWORD=REDACTED
|
||||||
|
KEYCLOAK_ADMIN=admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD=REDACTED
|
||||||
|
KEYCLOAK_HOSTNAME=YOUR_HOSTNAME
|
36
docker/compose/keycloakserver/compose.yaml
Normal file
36
docker/compose/keycloakserver/compose.yaml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
services:
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:26.0
|
||||||
|
container_name: keycloakserver
|
||||||
|
hostname: keycloakserver
|
||||||
|
command: start
|
||||||
|
environment:
|
||||||
|
KC_PROXY_ADDRESS_FORWARDING: true
|
||||||
|
KC_HOSTNAME_STRICT: false
|
||||||
|
KC_HOSTNAME: ${KEYCLOAK_HOSTNAME}
|
||||||
|
KC_PROXY: edge
|
||||||
|
KC_HTTP_ENABLED: true
|
||||||
|
KC_HEALTH_ENABLED: true
|
||||||
|
KC_HTTP_RELATIVE_PATH: /sso
|
||||||
|
KC_PROXY_HEADERS: xforwarded
|
||||||
|
PROXY_ADDRESS_FORWARDING: true
|
||||||
|
|
||||||
|
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
|
||||||
|
|
||||||
|
KC_DB: postgres
|
||||||
|
KC_DB_URL: jdbc:postgresql://keycloakpostgres/${POSTGRES_DB}
|
||||||
|
KC_DB_USERNAME: ${POSTGRES_USER}
|
||||||
|
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- keycloak-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
keycloak-network:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
|
2
docker/compose/keycloakserver/down.sh
Normal file
2
docker/compose/keycloakserver/down.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose down
|
||||||
|
|
1
docker/compose/keycloakserver/exec.sh
Normal file
1
docker/compose/keycloakserver/exec.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it keycloakserver bash
|
2
docker/compose/keycloakserver/logs.sh
Normal file
2
docker/compose/keycloakserver/logs.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose logs -f
|
||||||
|
|
2
docker/compose/keycloakserver/up.sh
Normal file
2
docker/compose/keycloakserver/up.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose up -d
|
||||||
|
|
1
docker/compose/nginx/.env
Normal file
1
docker/compose/nginx/.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
26
docker/compose/nginx/compose.yaml
Normal file
26
docker/compose/nginx/compose.yaml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
services:
|
||||||
|
overleafnginx:
|
||||||
|
image: nginx:stable-alpine
|
||||||
|
container_name: nginx
|
||||||
|
hostname: nginx
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- "/docker/compose/nginx/key.pem:/certs/nginx_key.pem:ro"
|
||||||
|
- "/docker/compose/nginx/ca.pem:/certs/nginx_certificate.pem:ro"
|
||||||
|
- "/docker/compose/nginx/nginx.conf:/etc/nginx/nginx.conf:ro"
|
||||||
|
ports:
|
||||||
|
- "0.0.0.0:443:443"
|
||||||
|
- "0.0.0.0:80:80"
|
||||||
|
environment:
|
||||||
|
NGINX_WORKER_PROCESSES: "4"
|
||||||
|
NGINX_WORKER_CONNECTIONS: "768"
|
||||||
|
networks:
|
||||||
|
- overleaf-network
|
||||||
|
- keycloak-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
overleaf-network:
|
||||||
|
external: true
|
||||||
|
keycloak-network:
|
||||||
|
external: true
|
||||||
|
|
4
docker/compose/nginx/cycle.sh
Normal file
4
docker/compose/nginx/cycle.sh
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
|
docker compose logs -f
|
||||||
|
|
2
docker/compose/nginx/down.sh
Normal file
2
docker/compose/nginx/down.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose down
|
||||||
|
|
2
docker/compose/nginx/logs.sh
Normal file
2
docker/compose/nginx/logs.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose logs -f
|
||||||
|
|
32
docker/compose/nginx/nginx_a.conf
Normal file
32
docker/compose/nginx/nginx_a.conf
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
events {}
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
ssl_certificate /certs/nginx_certificate.pem;
|
||||||
|
ssl_certificate_key /certs/nginx_key.pem;
|
||||||
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
|
||||||
|
server_tokens off;
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
location /sso {
|
||||||
|
proxy_pass http://keycloakserver:8080/sso;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Server $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
41
docker/compose/nginx/nginx_b.conf
Normal file
41
docker/compose/nginx/nginx_b.conf
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
events {}
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
ssl_certificate /certs/nginx_certificate.pem;
|
||||||
|
ssl_certificate_key /certs/nginx_key.pem;
|
||||||
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
|
||||||
|
server_tokens off;
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
location /sso {
|
||||||
|
proxy_pass http://keycloakserver:8080/sso;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Server $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
location /nodedev {
|
||||||
|
proxy_pass http://nodedev:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Server $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
docker/compose/nginx/nginx_c.conf
Normal file
97
docker/compose/nginx/nginx_c.conf
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
events {}
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
ssl_certificate /certs/nginx_certificate.pem;
|
||||||
|
ssl_certificate_key /certs/nginx_key.pem;
|
||||||
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
|
||||||
|
server_tokens off;
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://overleafserver:80;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
location /articles {
|
||||||
|
proxy_pass https://www.overleaf.com;
|
||||||
|
proxy_set_header Host www.overleaf.com;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /templates {
|
||||||
|
proxy_pass https://www.overleaf.com;
|
||||||
|
proxy_set_header Host www.overleaf.com;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /latex/templates {
|
||||||
|
proxy_pass https://www.overleaf.com;
|
||||||
|
proxy_set_header Host www.overleaf.com;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /learn {
|
||||||
|
proxy_pass https://www.overleaf.com;
|
||||||
|
proxy_set_header Host www.overleaf.com;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /sso {
|
||||||
|
proxy_pass http://keycloakserver:8080/sso;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Server $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
108
docker/compose/nginx/nginx_d.conf
Normal file
108
docker/compose/nginx/nginx_d.conf
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
events {}
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
ssl_certificate /certs/nginx_certificate.pem;
|
||||||
|
ssl_certificate_key /certs/nginx_key.pem;
|
||||||
|
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
|
||||||
|
server_tokens off;
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://overleafserver:80;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /register {
|
||||||
|
proxy_pass http://overleafregister:80;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /articles {
|
||||||
|
proxy_pass https://www.overleaf.com;
|
||||||
|
proxy_set_header Host www.overleaf.com;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /templates {
|
||||||
|
proxy_pass https://www.overleaf.com;
|
||||||
|
proxy_set_header Host www.overleaf.com;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /latex/templates {
|
||||||
|
proxy_pass https://www.overleaf.com;
|
||||||
|
proxy_set_header Host www.overleaf.com;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /learn {
|
||||||
|
proxy_pass https://www.overleaf.com;
|
||||||
|
proxy_set_header Host www.overleaf.com;
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_read_timeout 3m;
|
||||||
|
proxy_send_timeout 3m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /sso {
|
||||||
|
proxy_pass http://keycloakserver:8080/sso;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Server $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
docker/compose/nginx/up.sh
Normal file
2
docker/compose/nginx/up.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose up -d
|
||||||
|
|
1
docker/compose/overleafmongo/.env
Normal file
1
docker/compose/overleafmongo/.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
1
docker/compose/overleafmongo/backup.sh
Normal file
1
docker/compose/overleafmongo/backup.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec overleafmongo bash -c "mongodump --out /backup/"
|
33
docker/compose/overleafmongo/compose.yaml
Normal file
33
docker/compose/overleafmongo/compose.yaml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
services:
|
||||||
|
overleafmongo:
|
||||||
|
image: "mongo:6.0"
|
||||||
|
container_name: overleafmongo
|
||||||
|
hostname: overleafmongo
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: "mongosh --quiet --eval 'rs.hello().setName ? rs.hello().setName : rs.initiate({_id: \"overleaf\",members:[{_id: 0, host:\"overleafmongo:27017\"}]})'"
|
||||||
|
interval: 10s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
command: "--replSet overleaf"
|
||||||
|
expose:
|
||||||
|
- 27017
|
||||||
|
volumes:
|
||||||
|
- /docker/compose/overleafmongo/data_db:/data/db
|
||||||
|
- /docker/compose/overleafmongo/data_configdb:/data/configdb
|
||||||
|
- /docker/compose/overleafmongo/backup:/backup
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
networks:
|
||||||
|
- overleaf-network
|
||||||
|
extra_hosts:
|
||||||
|
- "mongo:127.0.0.1"
|
||||||
|
- "overleafmongo:127.0.0.1"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
overleaf_mongo:
|
||||||
|
overleaf_mongo_cdb:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
overleaf-network:
|
||||||
|
external: true
|
||||||
|
|
2
docker/compose/overleafmongo/down.sh
Normal file
2
docker/compose/overleafmongo/down.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose down
|
||||||
|
|
1
docker/compose/overleafmongo/exec.sh
Normal file
1
docker/compose/overleafmongo/exec.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it overleafmongo bash
|
2
docker/compose/overleafmongo/logs.sh
Normal file
2
docker/compose/overleafmongo/logs.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose logs -f
|
||||||
|
|
2
docker/compose/overleafmongo/up.sh
Normal file
2
docker/compose/overleafmongo/up.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose up -d
|
||||||
|
|
1
docker/compose/overleafredis/.env
Normal file
1
docker/compose/overleafredis/.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
6
docker/compose/overleafredis/README.md
Normal file
6
docker/compose/overleafredis/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
vm.overcommit_memory = 1
|
||||||
|
|
||||||
|
/etc/sysctl.conf
|
||||||
|
|
||||||
|
|
||||||
|
sysctl vm.overcommit_memory=1
|
1
docker/compose/overleafredis/backup.sh
Normal file
1
docker/compose/overleafredis/backup.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec overleafredis sh -c "cp -f dump.rdb /backup/"
|
31
docker/compose/overleafredis/compose.yaml
Normal file
31
docker/compose/overleafredis/compose.yaml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# docker network create overleaf-network
|
||||||
|
services:
|
||||||
|
overleafredis:
|
||||||
|
image: "redis:6.2-alpine"
|
||||||
|
container_name: overleafredis
|
||||||
|
hostname: overleafredis
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
|
||||||
|
start_period: 20s
|
||||||
|
interval: 30s
|
||||||
|
retries: 5
|
||||||
|
timeout: 3s
|
||||||
|
command: --save 60 1 --loglevel warning
|
||||||
|
volumes:
|
||||||
|
- /docker/compose/overleafredis/data:/data
|
||||||
|
- /docker/compose/overleafredis/backup:/backup
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
expose:
|
||||||
|
- 6379
|
||||||
|
networks:
|
||||||
|
- overleaf-network
|
||||||
|
environment:
|
||||||
|
REDIS_AOF_PERSISTENCE: "true"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
overleaf_redis:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
overleaf-network:
|
||||||
|
external: true
|
2
docker/compose/overleafredis/down.sh
Normal file
2
docker/compose/overleafredis/down.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose down
|
||||||
|
|
1
docker/compose/overleafredis/exec.sh
Normal file
1
docker/compose/overleafredis/exec.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it overleafredis sh
|
2
docker/compose/overleafredis/logs.sh
Normal file
2
docker/compose/overleafredis/logs.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose logs -f
|
||||||
|
|
2
docker/compose/overleafredis/up.sh
Normal file
2
docker/compose/overleafredis/up.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose up -d
|
||||||
|
|
1
docker/compose/overleafregister/.env
Normal file
1
docker/compose/overleafregister/.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
24
docker/compose/overleafregister/Dockerfile
Normal file
24
docker/compose/overleafregister/Dockerfile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
FROM python:3.12.5
|
||||||
|
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt -y install mc
|
||||||
|
RUN apt -y install docker.io
|
||||||
|
RUN pip install pymongo
|
||||||
|
RUN pip install email_validator
|
||||||
|
RUN pip install flask
|
||||||
|
RUN pip install gunicorn
|
||||||
|
RUN pip install requests
|
||||||
|
RUN pip install BeautifulSoup4
|
||||||
|
RUN apt -y install bash
|
||||||
|
RUN pip install --upgrade pip
|
||||||
|
RUN pip install flask_wtf
|
||||||
|
RUN pip install wtforms
|
||||||
|
RUN pip install flask_recaptcha
|
||||||
|
RUN pip install Markup
|
||||||
|
RUN pip install captcha Pillow
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/bash", "-c", "cd data && gunicorn wsgi:app --bind 0.0.0.0:80"]
|
||||||
|
|
||||||
|
|
57
docker/compose/overleafregister/README.md
Normal file
57
docker/compose/overleafregister/README.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
First of all, build the docker image:
|
||||||
|
|
||||||
|
```
|
||||||
|
>> sh make_image.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
* Build the docker image with make_image.sh
|
||||||
|
* In data you need to edit the following files:
|
||||||
|
* allowed_domains.json: This allows to configure domains endings (like uni-bremen.de which allows davrot@uni-bremen.de and davrot@neuro.uni-bremen.de) that are automatically allowed even if the person is not invited.
|
||||||
|
* blocked_users.json: Here you can name email addresses you want to block.
|
||||||
|
* config.json: Here you need to adapt the FQDNs and the two passwords:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"keycloak_url": "https://psintern.neuro.uni-bremen.de/sso",
|
||||||
|
"keycloak_login": "https://psintern.neuro.uni-bremen.de/login/oidc",
|
||||||
|
"admin_username": "automation@non.no",
|
||||||
|
"admin_password": "REDACTED",
|
||||||
|
"client_id": "admin-cli",
|
||||||
|
"client_secret": "REDACTED"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* Set a secret key in data/secret_key.json.
|
||||||
|
|
||||||
|
* In data/main.py: (pip install captcha flask email_validator pymongo)
|
||||||
|
* I simplied the captcha to 6x A . If you want something else change line 34.
|
||||||
|
* I am not using the generated captcha_image in the webpage. I thing modern machine learning makes no difference if it is a image or a text. I am using the captcha just to block simple bots. They cannot create an account anyhow. But if you want to, you can add it back to in templates/post.html in by using the "image"
|
||||||
|
```
|
||||||
|
<img src="data:image/png;base64,{{ captcha_image }}" alt="CAPTCHA">
|
||||||
|
```
|
||||||
|
* Change keycloak_url to your installation
|
||||||
|
|
||||||
|
* And finally change the logos in data/static and the text in templates/post.html
|
||||||
|
|
||||||
|
Start the container with:
|
||||||
|
|
||||||
|
```
|
||||||
|
>> sh up.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for development activate the
|
||||||
|
```
|
||||||
|
entrypoint: ["sh", "-c", "sleep infinity"]
|
||||||
|
```
|
||||||
|
in the compose.yaml. Then enter the container with
|
||||||
|
```
|
||||||
|
>> sh exec.sh
|
||||||
|
```
|
||||||
|
Inside the container you can use
|
||||||
|
```
|
||||||
|
>> cd data
|
||||||
|
>> sh run.sh
|
||||||
|
```
|
||||||
|
to start the server. Every change in the code, html file or logos requires run.sh to be stopped and started again!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
24
docker/compose/overleafregister/compose.yaml
Normal file
24
docker/compose/overleafregister/compose.yaml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
services:
|
||||||
|
overleafregister:
|
||||||
|
image: "overleafregister_image"
|
||||||
|
container_name: overleafregister
|
||||||
|
hostname: overleafregister
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- overleaf-network
|
||||||
|
- keycloak-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- /docker/compose/overleafregister/data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
# entrypoint: ["sh", "-c", "sleep infinity"]
|
||||||
|
|
||||||
|
networks:
|
||||||
|
|
||||||
|
keycloak-network:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
overleaf-network:
|
||||||
|
external: true
|
1
docker/compose/overleafregister/data/README.md
Normal file
1
docker/compose/overleafregister/data/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
82
docker/compose/overleafregister/data/add_user.py
Normal file
82
docker/compose/overleafregister/data/add_user.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import requests # type: ignore
|
||||||
|
import json
|
||||||
|
from requests.auth import HTTPBasicAuth # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def add_keycloak_user(username):
|
||||||
|
|
||||||
|
print(f"Start to create user {username} via OIDC")
|
||||||
|
|
||||||
|
with open("config.json", "r") as file:
|
||||||
|
config = json.load(file)
|
||||||
|
|
||||||
|
token_url = f"{config['keycloak_url']}/realms/master/protocol/openid-connect/token"
|
||||||
|
token_data = {
|
||||||
|
"grant_type": "password",
|
||||||
|
"username": config["admin_username"],
|
||||||
|
"password": config["admin_password"],
|
||||||
|
}
|
||||||
|
users_url = f"{config['keycloak_url']}/admin/realms/master/users"
|
||||||
|
|
||||||
|
print("-- Get token")
|
||||||
|
# Get token
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
token_url,
|
||||||
|
data=token_data,
|
||||||
|
auth=HTTPBasicAuth(config["client_id"], config["client_secret"]),
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
access_token = response.json()["access_token"]
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {access_token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if user exists
|
||||||
|
params = {"username": username, "exact": "true"}
|
||||||
|
|
||||||
|
print("-- Check if user exists")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(users_url, headers=headers, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Response is a list of users matching the criteria
|
||||||
|
users = response.json()
|
||||||
|
|
||||||
|
# If we found any users with exact username match, the user exists
|
||||||
|
if len(users) > 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("-- Make new user")
|
||||||
|
|
||||||
|
# Make new user
|
||||||
|
new_user = {
|
||||||
|
"username": username,
|
||||||
|
"enabled": True,
|
||||||
|
"emailVerified": False,
|
||||||
|
"firstName": " ",
|
||||||
|
"lastName": " ",
|
||||||
|
"email": username,
|
||||||
|
"requiredActions": ["UPDATE_PASSWORD"],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create the user
|
||||||
|
response = requests.post(users_url, headers=headers, data=json.dumps(new_user))
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("-- DONE")
|
||||||
|
|
||||||
|
return True
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"allowed_domains": [
|
||||||
|
"uni-bremen.de"
|
||||||
|
]
|
||||||
|
}
|
6
docker/compose/overleafregister/data/blocked_users.json
Normal file
6
docker/compose/overleafregister/data/blocked_users.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"blocked_users": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
15
docker/compose/overleafregister/data/check_invites.py
Normal file
15
docker/compose/overleafregister/data/check_invites.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
|
||||||
|
def check_invites(
|
||||||
|
email_to_find: str, container_name: str = "overleafmongo", port: int = 27017
|
||||||
|
) -> bool:
|
||||||
|
client = pymongo.MongoClient(container_name, port)
|
||||||
|
db = client.sharelatex
|
||||||
|
project_invites = db.projectInvites
|
||||||
|
|
||||||
|
search_result = project_invites.find_one({"email": email_to_find})
|
||||||
|
if search_result is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
15
docker/compose/overleafregister/data/check_user.py
Normal file
15
docker/compose/overleafregister/data/check_user.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
|
||||||
|
def check_user(
|
||||||
|
email_to_find: str, container_name: str = "overleafmongo", port: int = 27017
|
||||||
|
) -> bool:
|
||||||
|
client = pymongo.MongoClient(container_name, port)
|
||||||
|
db = client.sharelatex
|
||||||
|
users = db.users
|
||||||
|
|
||||||
|
search_result = users.find_one({"email": email_to_find})
|
||||||
|
if search_result is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
8
docker/compose/overleafregister/data/config.json
Normal file
8
docker/compose/overleafregister/data/config.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"keycloak_url": "https://overleaf.fb1.uni-bremen.de/sso",
|
||||||
|
"keycloak_login": "https://overleaf.pip.uni-bremen.de/login/oidc",
|
||||||
|
"admin_username": "automation@non.no",
|
||||||
|
"admin_password": "REDACTED",
|
||||||
|
"client_id": "admin-cli",
|
||||||
|
"client_secret": "REDACTED"
|
||||||
|
}
|
70
docker/compose/overleafregister/data/main.py
Normal file
70
docker/compose/overleafregister/data/main.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import json
|
||||||
|
from flask import (
|
||||||
|
Flask,
|
||||||
|
render_template,
|
||||||
|
request,
|
||||||
|
Response,
|
||||||
|
send_from_directory,
|
||||||
|
session,
|
||||||
|
redirect,
|
||||||
|
)
|
||||||
|
from io import BytesIO
|
||||||
|
from captcha.image import ImageCaptcha
|
||||||
|
import random
|
||||||
|
import base64
|
||||||
|
from process_emails import process_emails
|
||||||
|
|
||||||
|
with open("config.json", "r") as file:
|
||||||
|
config: dict = json.load(file)
|
||||||
|
|
||||||
|
container_name_mongo: str = "overleafmongo"
|
||||||
|
port_mongo: int = 27017
|
||||||
|
container_name_overleaf: str = "overleafserver"
|
||||||
|
keycloak_url: str = config["keycloak_login"]
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
with open("secret_key.json", "r") as file:
|
||||||
|
secret_key: dict = json.load(file)
|
||||||
|
|
||||||
|
assert secret_key is not None
|
||||||
|
assert secret_key["secret_key"] is not None
|
||||||
|
app.config["SECRET_KEY"] = secret_key["secret_key"]
|
||||||
|
|
||||||
|
|
||||||
|
def generate_captcha():
|
||||||
|
image = ImageCaptcha(width=280, height=90)
|
||||||
|
# I simplied the Captcha
|
||||||
|
captcha_text = "".join(random.choices("A", k=6))
|
||||||
|
data = image.generate(captcha_text)
|
||||||
|
return captcha_text, data
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/register", methods=["GET", "POST"])
|
||||||
|
def index() -> Response:
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
captcha_text, captcha_image = generate_captcha()
|
||||||
|
session["captcha"] = captcha_text
|
||||||
|
captcha_base64 = base64.b64encode(captcha_image.getvalue()).decode("utf-8")
|
||||||
|
return render_template("post.html", captcha_image=captcha_base64)
|
||||||
|
|
||||||
|
elif request.method == "POST":
|
||||||
|
email = request.form.get("email")
|
||||||
|
user_captcha = request.form.get("captcha")
|
||||||
|
|
||||||
|
if user_captcha and user_captcha.upper() == session.get("captcha"):
|
||||||
|
if process_emails(
|
||||||
|
mail_address=email,
|
||||||
|
container_name_mongo=container_name_mongo,
|
||||||
|
port_mongo=port_mongo,
|
||||||
|
):
|
||||||
|
return redirect(keycloak_url)
|
||||||
|
else:
|
||||||
|
return f"We couldn't register your email {email}."
|
||||||
|
else:
|
||||||
|
return "There was a problem with solving the captcha. Try again. Sorry!"
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/register/static/<path:path>", methods=["GET"])
|
||||||
|
def serve_static_files(path) -> Response:
|
||||||
|
return send_from_directory("static", path)
|
63
docker/compose/overleafregister/data/process_emails.py
Normal file
63
docker/compose/overleafregister/data/process_emails.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
from email_validator import validate_email # type: ignore
|
||||||
|
import email_validator
|
||||||
|
import json
|
||||||
|
|
||||||
|
from check_invites import check_invites
|
||||||
|
from check_user import check_user
|
||||||
|
from add_user import add_keycloak_user
|
||||||
|
|
||||||
|
|
||||||
|
def process_emails(
|
||||||
|
mail_address: str,
|
||||||
|
config_file: str = "allowed_domains.json",
|
||||||
|
blocked_user_file: str = "blocked_users.json",
|
||||||
|
container_name_mongo: str = "overleafmongo",
|
||||||
|
port_mongo: int = 27017,
|
||||||
|
) -> bool:
|
||||||
|
|
||||||
|
with open(config_file, "r") as file:
|
||||||
|
allowed_domains: dict = json.load(file)
|
||||||
|
|
||||||
|
with open(blocked_user_file, "r") as file:
|
||||||
|
blocked_users: dict = json.load(file)
|
||||||
|
|
||||||
|
if (mail_address == "") or (mail_address is None):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
emailinfo = validate_email(mail_address, check_deliverability=False)
|
||||||
|
mail_address = emailinfo.normalized
|
||||||
|
except email_validator.exceptions_types.EmailSyntaxError:
|
||||||
|
return False
|
||||||
|
except email_validator.exceptions_types.EmailNotValidError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for blocked_user in blocked_users["blocked_users"]:
|
||||||
|
if mail_address == blocked_user:
|
||||||
|
return False
|
||||||
|
print(f"{mail_address} -- is not blocked")
|
||||||
|
|
||||||
|
is_email_allowed: bool = False
|
||||||
|
|
||||||
|
if check_invites(
|
||||||
|
email_to_find=mail_address, container_name=container_name_mongo, port=port_mongo
|
||||||
|
):
|
||||||
|
is_email_allowed = True
|
||||||
|
print(f"{mail_address} -- eMail is invited")
|
||||||
|
|
||||||
|
if check_user(
|
||||||
|
email_to_find=mail_address, container_name=container_name_mongo, port=port_mongo
|
||||||
|
):
|
||||||
|
is_email_allowed = True
|
||||||
|
print(f"{mail_address} -- eMail is already registered")
|
||||||
|
|
||||||
|
if is_email_allowed is False:
|
||||||
|
domain_found: bool = False
|
||||||
|
for domain in allowed_domains["allowed_domains"]:
|
||||||
|
if mail_address.endswith(domain):
|
||||||
|
domain_found = True
|
||||||
|
print(f"{mail_address} -- domain was found")
|
||||||
|
|
||||||
|
if domain_found is False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return add_keycloak_user(mail_address)
|
1
docker/compose/overleafregister/data/run.sh
Normal file
1
docker/compose/overleafregister/data/run.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
gunicorn wsgi:app --bind 0.0.0.0:80
|
4
docker/compose/overleafregister/data/secret_key.json
Normal file
4
docker/compose/overleafregister/data/secret_key.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"secret_key": "REDACTED"
|
||||||
|
}
|
||||||
|
|
7
docker/compose/overleafregister/data/static/hajtex.svg
Normal file
7
docker/compose/overleafregister/data/static/hajtex.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.3 KiB |
96
docker/compose/overleafregister/data/templates/post.html
Normal file
96
docker/compose/overleafregister/data/templates/post.html
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Register your HajTex account</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="email"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 150px;
|
||||||
|
width: 100%;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-marker {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<img src="/register/static/hajtex.svg" alt="Logo Hajtex" style="max-width: 200px;">
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<h1>Register your HajTex account</h1>
|
||||||
|
|
||||||
|
<h2>Who can register?</h2>
|
||||||
|
1. You don't need to register here if your OIDC account is ready for you. Got here to login <a href="/login">login</a>. <p>
|
||||||
|
2. If someone has <font color="red"><b>invited</b></font> you <font color="red"><b>to a project</b></font>.<p>
|
||||||
|
|
||||||
|
In the case of 2. use this form. Afterwards set your password via the "Forgot Password?" option during the login process.
|
||||||
|
|
||||||
|
<form method="POST" id="demo-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<input type="email" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="captcha">CAPTCHA:</label>
|
||||||
|
<input type="text" id="captcha" name="captcha" maxlength="6" size="6" required>
|
||||||
|
</div>
|
||||||
|
Please enter the following six letters: <font color="green"><b>AAAAAA</b></font>
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="submit-btn">Register</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
4
docker/compose/overleafregister/data/wsgi.py
Normal file
4
docker/compose/overleafregister/data/wsgi.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
from main import app
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
2
docker/compose/overleafregister/down.sh
Normal file
2
docker/compose/overleafregister/down.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose down
|
||||||
|
|
1
docker/compose/overleafregister/exec.sh
Normal file
1
docker/compose/overleafregister/exec.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it overleafregister bash
|
2
docker/compose/overleafregister/logs.sh
Normal file
2
docker/compose/overleafregister/logs.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose logs -f
|
||||||
|
|
1
docker/compose/overleafregister/make_image.sh
Normal file
1
docker/compose/overleafregister/make_image.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker build --network host -t overleafregister_image .
|
2
docker/compose/overleafregister/up.sh
Normal file
2
docker/compose/overleafregister/up.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose up -d
|
||||||
|
|
1
docker/compose/overleafserver/.env
Normal file
1
docker/compose/overleafserver/.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
77
docker/compose/overleafserver/build_env.sh
Normal file
77
docker/compose/overleafserver/build_env.sh
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
FQDN="psintern.neuro.uni-bremen.de"
|
||||||
|
|
||||||
|
# KeyCloak
|
||||||
|
OIDC_CLIENT_ID=overleaf
|
||||||
|
OIDC_CLIENT_SECRET=REDACTED
|
||||||
|
OIDC_ENABLE=true
|
||||||
|
OIDC_NAME_SHORT="OIDC"
|
||||||
|
OIDC_NAME_LONG="OIDC"
|
||||||
|
|
||||||
|
# Email
|
||||||
|
OVERLEAF_EMAIL_PASSWORD=REDACTED
|
||||||
|
OVERLEAF_EMAIL_FROM_ADDRESS=overleaf@uni-bremen.de
|
||||||
|
OVERLEAF_EMAIL_SMTP_HOST=smtp.uni-bremen.de
|
||||||
|
OVERLEAF_EMAIL_SMTP_PORT=465
|
||||||
|
OVERLEAF_EMAIL_SMTP_SECURE=true
|
||||||
|
OVERLEAF_EMAIL_SMTP_USER=overleaf
|
||||||
|
|
||||||
|
# Other
|
||||||
|
OVERLEAF_APP_NAME="University of Bremen -- HajTex"
|
||||||
|
OVERLEAF_NAV_TITLE="Uni Bremen HajTex"
|
||||||
|
OVERLEAF_CUSTOM_EMAIL_FOOTER="University of Bremen -- HajTex"
|
||||||
|
|
||||||
|
# ##################################################
|
||||||
|
|
||||||
|
OVERLEAF_SITE_URL=https://${FQDN}
|
||||||
|
URL=https://${FQDN}/sso/realms/master/.well-known/openid-configuration
|
||||||
|
OIDC_CALLBACK_URL=https://${FQDN}/login/oidc/callback
|
||||||
|
|
||||||
|
echo ${URL}
|
||||||
|
wget -O openid-configuration ${URL}
|
||||||
|
|
||||||
|
echo OIDC_ISSUER
|
||||||
|
OIDC_ISSUER=$(cat openid-configuration | sed s/","/"\n"/g | grep \"issuer\" | sed s/'\":'/'\n'/g | grep https | sed s/'\"'/''/g)
|
||||||
|
echo $OIDC_ISSUER
|
||||||
|
|
||||||
|
echo OIDC_AUTHORIZATION_URL
|
||||||
|
OIDC_AUTHORIZATION_URL=$(cat openid-configuration | sed s/","/"\n"/g | grep ^\"authorization_endpoint\" | sed s/'\":'/'\n'/g | grep https | sed s/'\"'/''/g | head -1)
|
||||||
|
echo $OIDC_AUTHORIZATION_URL
|
||||||
|
|
||||||
|
echo OIDC_TOKEN_URL
|
||||||
|
OIDC_TOKEN_URL=$(cat openid-configuration | sed s/","/"\n"/g | grep ^\"token_endpoint\" | sed s/'\":'/'\n'/g | grep https | sed s/'\"'/''/g | head -1)
|
||||||
|
echo $OIDC_TOKEN_URL
|
||||||
|
|
||||||
|
echo OIDC_USERINFO_URL
|
||||||
|
OIDC_USERINFO_URL=$(cat openid-configuration | sed s/","/"\n"/g | grep ^\"userinfo_endpoint\" | sed s/'\":'/'\n'/g | grep https | sed s/'\"'/''/g | head -1)
|
||||||
|
echo $OIDC_USERINFO_URL
|
||||||
|
|
||||||
|
echo "# Keycloak OpenID Connect Configuration" > .env
|
||||||
|
echo "OIDC_ENABLE=${OIDC_ENABLE}" >> .env
|
||||||
|
echo "OIDC_NAME_SHORT=${OIDC_NAME_SHORT}" >> .env
|
||||||
|
echo "OIDC_NAME_LONG=${OIDC_NAME_LONG}" >> .env
|
||||||
|
echo "OIDC_ISSUER=${OIDC_ISSUER}" >> .env
|
||||||
|
echo "OIDC_AUTHORIZATION_URL=${OIDC_AUTHORIZATION_URL}" >> .env
|
||||||
|
echo "OIDC_TOKEN_URL=${OIDC_TOKEN_URL}" >> .env
|
||||||
|
echo "OIDC_USERINFO_URL=${OIDC_USERINFO_URL}" >> .env
|
||||||
|
echo "OIDC_CLIENT_ID=${OIDC_CLIENT_ID}" >> .env
|
||||||
|
echo "OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}" >> .env
|
||||||
|
echo "OIDC_CALLBACK_URL=${OIDC_CALLBACK_URL}" >> .env
|
||||||
|
rm openid-configuration
|
||||||
|
echo "" >> .env
|
||||||
|
|
||||||
|
echo "# eMail Account Configuration" >> .env
|
||||||
|
echo "OVERLEAF_EMAIL_PASSWORD=${OVERLEAF_EMAIL_PASSWORD}" >> .env
|
||||||
|
echo "OVERLEAF_EMAIL_FROM_ADDRESS=${OVERLEAF_EMAIL_FROM_ADDRESS}" >> .env
|
||||||
|
echo "OVERLEAF_EMAIL_SMTP_HOST=${OVERLEAF_EMAIL_SMTP_HOST}" >> .env
|
||||||
|
echo "OVERLEAF_EMAIL_SMTP_PORT=${OVERLEAF_EMAIL_SMTP_PORT}" >> .env
|
||||||
|
echo "OVERLEAF_EMAIL_SMTP_SECURE=${OVERLEAF_EMAIL_SMTP_SECURE}" >> .env
|
||||||
|
echo "OVERLEAF_EMAIL_SMTP_USER=${OVERLEAF_EMAIL_SMTP_USER}" >> .env
|
||||||
|
echo "" >> .env
|
||||||
|
|
||||||
|
echo "# Other Overleaf Configurations Configuration" >> .env
|
||||||
|
echo "OVERLEAF_SITE_URL=${OVERLEAF_SITE_URL}" >> .env
|
||||||
|
echo "OVERLEAF_ADMIN_EMAIL=${OVERLEAF_EMAIL_FROM_ADDRESS}" >> .env
|
||||||
|
echo "OVERLEAF_APP_NAME=\"${OVERLEAF_APP_NAME}\"" >> .env
|
||||||
|
echo "OVERLEAF_NAV_TITLE=\"${OVERLEAF_NAV_TITLE}\"" >> .env
|
||||||
|
echo "OVERLEAF_CUSTOM_EMAIL_FOOTER=\"${OVERLEAF_CUSTOM_EMAIL_FOOTER}\"" >> .env
|
||||||
|
|
1
docker/compose/overleafserver/data/prep.sh
Normal file
1
docker/compose/overleafserver/data/prep.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
2
docker/compose/overleafserver/down.sh
Normal file
2
docker/compose/overleafserver/down.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose down
|
||||||
|
|
1
docker/compose/overleafserver/exec.sh
Normal file
1
docker/compose/overleafserver/exec.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker exec -it overleafserver bash
|
2
docker/compose/overleafserver/logs.sh
Normal file
2
docker/compose/overleafserver/logs.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
docker compose logs -f
|
||||||
|
|
7
docker/compose/overleafserver/up.sh
Normal file
7
docker/compose/overleafserver/up.sh
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
docker compose down
|
||||||
|
cd /docker/features
|
||||||
|
sh _tools/configure_features.sh
|
||||||
|
sh _tools/generate_prep.sh
|
||||||
|
cd /docker/compose/overleafserver
|
||||||
|
docker compose up -d
|
||||||
|
|
1
docker/compose/pull_texlive.sh
Normal file
1
docker/compose/pull_texlive.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker pull texlive/texlive:latest-full
|
BIN
docker/compose/scp_git_bridge/01.png
Normal file
BIN
docker/compose/scp_git_bridge/01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
docker/compose/scp_git_bridge/02.png
Normal file
BIN
docker/compose/scp_git_bridge/02.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
BIN
docker/compose/scp_git_bridge/03.png
Normal file
BIN
docker/compose/scp_git_bridge/03.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
41
docker/compose/scp_git_bridge/Dockerfile
Normal file
41
docker/compose/scp_git_bridge/Dockerfile
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
# Ensure non-interactive installation
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Update and install packages
|
||||||
|
RUN apt update
|
||||||
|
RUN apt -y install mc
|
||||||
|
RUN apt -y install bash
|
||||||
|
RUN apt -y install openssh-server
|
||||||
|
RUN apt -y install python3-pip
|
||||||
|
RUN apt -y install python3-argh
|
||||||
|
RUN apt -y install git-all
|
||||||
|
RUN apt -y install golang-go
|
||||||
|
RUN apt -y install curl
|
||||||
|
RUN apt -y install openssl
|
||||||
|
RUN apt -y install libpam-script
|
||||||
|
RUN apt -y install sudo
|
||||||
|
RUN apt -y install rush
|
||||||
|
RUN apt -y install inetutils-syslogd
|
||||||
|
RUN apt -y install python3-docker
|
||||||
|
|
||||||
|
RUN mkdir -p /compile && cd /compile && git clone https://github.com/kha7iq/kc-ssh-pam.git && cd /compile/kc-ssh-pam && go build && mkdir -p /etc/kc-ssh-pam && cp /compile/kc-ssh-pam/kc-ssh-pam /etc/kc-ssh-pam
|
||||||
|
|
||||||
|
RUN cp -a /etc /etc_original
|
||||||
|
RUN rm -f /etc_original/hostname
|
||||||
|
RUN rm -f /etc_original/hosts
|
||||||
|
RUN rm -f /etc_original/resolv.conf
|
||||||
|
|
||||||
|
# Copy initialization script
|
||||||
|
COPY files/init.sh /init.sh
|
||||||
|
RUN chmod +x /init.sh
|
||||||
|
|
||||||
|
# Expose SSH port
|
||||||
|
EXPOSE 22
|
||||||
|
|
||||||
|
ENTRYPOINT ["/init.sh"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
156
docker/compose/scp_git_bridge/README.md
Normal file
156
docker/compose/scp_git_bridge/README.md
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
If the user logs in via git (in the moment on port 993, please don't forget to allow port 993 via ufw allow 993), the projects for that user are automatically updated.
|
||||||
|
|
||||||
|
Every 5 minutes, cron checks the userdata base of overleaf and new user from the database are created.
|
||||||
|
|
||||||
|
## Get the ssh keys for a user
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone ssh://[USERNAME]@[FQDN]:[PORT]/sshkey.git
|
||||||
|
```
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone ssh://davrot@uni-bremen.de@psintern.neuro.uni-bremen.de:993/sshkey.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get the project list for a user
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone ssh://[USERNAME]@[FQDN]:[PORT]/projects.git
|
||||||
|
```
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone ssh://davrot@uni-bremen.de@psintern.neuro.uni-bremen.de:993/projects.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get a project
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone ssh://[USERNAME]@[FQDN]:[PORT]/[PROJECT_ID].git
|
||||||
|
```
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone ssh://davrot@uni-bremen.de@psintern.neuro.uni-bremen.de:993/6759fdf66ca7b8bc5b81b184.git
|
||||||
|
```
|
||||||
|
|
||||||
|
On the one side this backup container communicates with the user via git and with the overleaf server via docker socket.
|
||||||
|
|
||||||
|
Don't forget the crontab entry for host:
|
||||||
|
|
||||||
|
```
|
||||||
|
# m h dom mon dow command
|
||||||
|
*/5 * * * * sh /docker/compose/hajtex_sshd/exec_update_userlist.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise, login will fail without the user directories. You can also run it manually:
|
||||||
|
```
|
||||||
|
sh /docker/compose/hajtex_sshd/exec_update_userlist.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
# Port 993
|
||||||
|
|
||||||
|
If you don't like port 993 you can change the compose.yaml
|
||||||
|
```
|
||||||
|
ports:
|
||||||
|
- 993:22
|
||||||
|
```
|
||||||
|
accordingly. But don't forget you firewall:
|
||||||
|
|
||||||
|
```
|
||||||
|
ufw allow 993:22
|
||||||
|
```
|
||||||
|
|
||||||
|
# ssh / scp / git-shell authentification against KeyCloak
|
||||||
|
|
||||||
|
## Create the client in keycloak:
|
||||||
|
|
||||||
|
```
|
||||||
|
urn:ietf:wg:oauth:2.0:oob
|
||||||
|
```
|
||||||
|
|
||||||
|
![A](01.png)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
![B](02.png)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
![C](03.png)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Update files/config.toml
|
||||||
|
|
||||||
|
Change clientsecret and the endpoint.
|
||||||
|
|
||||||
|
```
|
||||||
|
realm = "master"
|
||||||
|
endpoint = "https://psintern.neuro.uni-bremen.de/sso/"
|
||||||
|
clientid = "linux-ssh"
|
||||||
|
clientsecret = "REDACTED"
|
||||||
|
clientscope = "openid"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create image:
|
||||||
|
|
||||||
|
```
|
||||||
|
>> make_image.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Change the name of the HajTex server container:
|
||||||
|
|
||||||
|
Default is "/overleafserver"
|
||||||
|
|
||||||
|
If your installation is different then change in the files download_files.py, auth_against_docker.py and update_userlist.py modifiy the line accordingly:
|
||||||
|
|
||||||
|
```
|
||||||
|
container_name: str = "/overleafserver",
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Files
|
||||||
|
|
||||||
|
* Dockerfile
|
||||||
|
|
||||||
|
Dockerfile for creating the container image
|
||||||
|
|
||||||
|
* compose.yaml
|
||||||
|
|
||||||
|
Compose file to start the container
|
||||||
|
|
||||||
|
* crontab_host.txt
|
||||||
|
|
||||||
|
This needs to be placed into the crontab of the host
|
||||||
|
|
||||||
|
* down.sh
|
||||||
|
|
||||||
|
For stoping the container
|
||||||
|
|
||||||
|
* exec.sh
|
||||||
|
|
||||||
|
For entering the container for an interactive session
|
||||||
|
|
||||||
|
* init.sh
|
||||||
|
|
||||||
|
Init script that is ran during starting the container. The make_image.sh places it into the container.
|
||||||
|
|
||||||
|
* logs.sh
|
||||||
|
|
||||||
|
Shows the logs of the running container
|
||||||
|
|
||||||
|
* make_image.sh
|
||||||
|
|
||||||
|
Needs to be run for generating the container image
|
||||||
|
|
||||||
|
* exec_update_userlist.sh
|
||||||
|
|
||||||
|
Is run by the cron to update the user basis in the container based on the overleaf user database
|
||||||
|
|
44
docker/compose/scp_git_bridge/compose.yaml
Normal file
44
docker/compose/scp_git_bridge/compose.yaml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
services:
|
||||||
|
hajtexsshd:
|
||||||
|
image: hajtex_sshd_image
|
||||||
|
container_name: hajtexsshd
|
||||||
|
hostname: hajtexsshd
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /docker/compose/scp_git_bridge/downloads:/downloads
|
||||||
|
- /docker/compose/scp_git_bridge/etc:/etc
|
||||||
|
- /docker/compose/scp_git_bridge/log:/var/log
|
||||||
|
|
||||||
|
- /docker/compose/scp_git_bridge/files/auth_against_docker.py:/auth_against_docker.py:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/build_jail.sh:/build_jail.sh:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/download_files.py:/download_files.py:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/get_projects.py:/get_projects.py:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/pam_sshd:/etc/pam.d/sshd:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/process_user_auth.sh:/process_user_auth.sh:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/sshd_config:/etc/ssh/sshd_config:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/update_user_jail.sh:/update_user_jail.sh:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/update_userlist.py:/update_userlist.py:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/rush.rc:/etc/rush.rc:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/pre-rush.sh:/pre-rush.sh:ro
|
||||||
|
- /docker/compose/scp_git_bridge/files/update_project_list.py:/update_project_list.py:ro
|
||||||
|
|
||||||
|
- /docker/compose/scp_git_bridge/files/config.toml:/etc/kc-ssh-pam/config.toml:ro
|
||||||
|
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- 993:22
|
||||||
|
environment:
|
||||||
|
PUID: 1000
|
||||||
|
PGID: 1000
|
||||||
|
TZ: Etc/UTC
|
||||||
|
networks:
|
||||||
|
- overleaf-network
|
||||||
|
- keycloak-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
overleaf-network:
|
||||||
|
external: true
|
||||||
|
keycloak-network:
|
||||||
|
external: true
|
||||||
|
|
2
docker/compose/scp_git_bridge/crontab_host.txt
Normal file
2
docker/compose/scp_git_bridge/crontab_host.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# m h dom mon dow command
|
||||||
|
*/5 * * * * sh /docker/compose/scp_git_bridge/exec_update_userlist.sh
|
22
docker/compose/scp_git_bridge/docker_tools/README.md
Normal file
22
docker/compose/scp_git_bridge/docker_tools/README.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
These tools need to be placed inside the overleaf docker container. We do this automatically when this container goes up via the install.sh.
|
||||||
|
|
||||||
|
* auth_check_user.js
|
||||||
|
|
||||||
|
Is used for checking a user and the password against the overleaf user database
|
||||||
|
|
||||||
|
* download_zip.js
|
||||||
|
|
||||||
|
Downloads the data for a project id into a zip file.
|
||||||
|
|
||||||
|
* export_project_list_of_user.js
|
||||||
|
|
||||||
|
Given a userid the get a list of the project ids of the user
|
||||||
|
|
||||||
|
* get_user_list.js
|
||||||
|
|
||||||
|
We get the list of the list of all users in the overleaf user database
|
||||||
|
|
||||||
|
* id_user.js
|
||||||
|
|
||||||
|
We get the userid for a username (==email)
|
|
@ -0,0 +1,66 @@
|
||||||
|
const { waitForDb } = require('/overleaf/services/web/app/src/infrastructure/mongodb')
|
||||||
|
const { User } = require('/overleaf/services/web/app/src/models/User')
|
||||||
|
const bcrypt = require('/overleaf/services/web/node_modules/bcrypt')
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 2) {
|
||||||
|
console.error('Usage: node auth_check_user.js <username> <password>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const username = args[0];
|
||||||
|
const password = args[1];
|
||||||
|
const query = {"email": username };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await waitForDb()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Cannot connect to mongodb')
|
||||||
|
process.exit(1); // fail
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOne(query).exec();
|
||||||
|
|
||||||
|
if (!user || !user.hashedPassword) {
|
||||||
|
process.exit(1); // fail
|
||||||
|
}
|
||||||
|
|
||||||
|
let rounds = 0
|
||||||
|
try {
|
||||||
|
rounds = bcrypt.getRounds(user.hashedPassword)
|
||||||
|
} catch (err) {
|
||||||
|
let prefix, suffix, length
|
||||||
|
if (typeof user.hashedPassword === 'string') {
|
||||||
|
length = user.hashedPassword.length
|
||||||
|
if (user.hashedPassword.length > 50) {
|
||||||
|
// A full bcrypt hash is 60 characters long.
|
||||||
|
prefix = user.hashedPassword.slice(0, '$2a$12$x'.length)
|
||||||
|
suffix = user.hashedPassword.slice(-4)
|
||||||
|
} else if (user.hashedPassword.length > 20) {
|
||||||
|
prefix = user.hashedPassword.slice(0, 4)
|
||||||
|
suffix = user.hashedPassword.slice(-4)
|
||||||
|
} else {
|
||||||
|
prefix = user.hashedPassword.slice(0, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(1); // fail
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = await bcrypt.compare(password, user.hashedPassword)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
process.exit(1); // fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error("An unexpected error occurred:", err); // Catch any unexpected errors
|
||||||
|
process.exit(1); // fail
|
||||||
|
});
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue