add forgejo, add jupyter
This commit is contained in:
parent
a6d498bda0
commit
98417edd8b
23 changed files with 562 additions and 261 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
|
|
@ -45,9 +45,9 @@ BOOKSTACK_APP_KEY=base64:YOUR_BOOKSTACK_KEY_HERE
|
||||||
# ========================================
|
# ========================================
|
||||||
DB_HOST=bookstack-mariadb
|
DB_HOST=bookstack-mariadb
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
DB_DATABASE=bookstack
|
BOOKSTACK_DB_DATABASE=bookstack
|
||||||
DB_USERNAME=bookstack
|
BOOKSTACK_DB_USERNAME=bookstack
|
||||||
DB_PASSWORD=your_secure_database_password_here
|
BOOKSTACK_DB_PASSWORD=your_secure_database_password_here
|
||||||
|
|
||||||
# MariaDB root password
|
# MariaDB root password
|
||||||
MARIADB_ROOT_PASSWORD=your_secure_root_password_here
|
MARIADB_ROOT_PASSWORD=your_secure_root_password_here
|
||||||
|
|
|
||||||
7
.github/copilot-instructions.md
vendored
7
.github/copilot-instructions.md
vendored
|
|
@ -71,6 +71,13 @@ BOOKSTACK_PORT=6875
|
||||||
# BookStack/DB configs (see docker-compose.yml for full list)
|
# BookStack/DB configs (see docker-compose.yml for full list)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: this repository also contains a local `.env` with example credentials (do NOT commit real secrets). Current repository `.env` contents:
|
||||||
|
```properties
|
||||||
|
MATRIX_USER=einszwovier_bot
|
||||||
|
MATRIX_PASS=einszwo4
|
||||||
|
```
|
||||||
|
Prefer injecting secrets via CI environment variables or a secure vault for production. Add `MATRIX_ROOM` and/or `MATRIX_HOMESERVER` when testing Matrix flows.
|
||||||
|
|
||||||
### Debugging Matrix Integration
|
### Debugging Matrix Integration
|
||||||
Use `get_room_id.py` to discover Matrix room IDs:
|
Use `get_room_id.py` to discover Matrix room IDs:
|
||||||
```python
|
```python
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -103,3 +103,5 @@ dmypy.json
|
||||||
*.db-wal
|
*.db-wal
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
.github
|
||||||
|
.venv
|
||||||
|
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
# Portainer Quick Reference
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### Access Portainer
|
|
||||||
```
|
|
||||||
http://localhost:9000
|
|
||||||
or
|
|
||||||
http://einszwovier.local:9000
|
|
||||||
```
|
|
||||||
|
|
||||||
### First Login
|
|
||||||
1. Create admin account
|
|
||||||
2. Select "Local" environment
|
|
||||||
3. Done!
|
|
||||||
|
|
||||||
## 📊 Stack Overview: studio-einszwovier
|
|
||||||
|
|
||||||
| Service | Port | Health Check | Purpose |
|
|
||||||
|---------|------|--------------|---------|
|
|
||||||
| web | 80 | ✅ | PDF Cost Calculator |
|
|
||||||
| bookstack | 6875 | ✅ | Documentation Wiki |
|
|
||||||
| bookstack-mariadb | - | ✅ | Database |
|
|
||||||
| synapse | 8008 | ✅ | Matrix Server |
|
|
||||||
| ollama | 11434 | ✅ | Local LLM |
|
|
||||||
| open-webui | 8080 | ✅ | LLM Interface |
|
|
||||||
| watchtower | - | ✅ | Auto-Updates |
|
|
||||||
| portainer | 9000 | ✅ | Management UI |
|
|
||||||
|
|
||||||
## 🎯 Common Tasks
|
|
||||||
|
|
||||||
### View All Services
|
|
||||||
**Portainer**: Stacks → studio-einszwovier
|
|
||||||
**CLI**: `docker-compose ps`
|
|
||||||
|
|
||||||
### Restart a Service
|
|
||||||
**Portainer**: Containers → [service] → Restart
|
|
||||||
**CLI**: `docker-compose restart [service]`
|
|
||||||
|
|
||||||
### View Logs
|
|
||||||
**Portainer**: Containers → [service] → Logs
|
|
||||||
**CLI**: `docker-compose logs -f [service]`
|
|
||||||
|
|
||||||
### Check Health Status
|
|
||||||
**Portainer**: Containers → Look for 🟢/🔴 indicator
|
|
||||||
**CLI**: `docker-compose ps` (shows (healthy) or (unhealthy))
|
|
||||||
|
|
||||||
### Stop Everything
|
|
||||||
**Portainer**: Stacks → studio-einszwovier → Stop
|
|
||||||
**CLI**: `docker-compose stop`
|
|
||||||
|
|
||||||
### Start Everything
|
|
||||||
**Portainer**: Stacks → studio-einszwovier → Start
|
|
||||||
**CLI**: `docker-compose start`
|
|
||||||
|
|
||||||
### Update a Service
|
|
||||||
**Portainer**: Containers → [service] → Recreate
|
|
||||||
**CLI**: `docker-compose pull [service] && docker-compose up -d [service]`
|
|
||||||
|
|
||||||
## 🔍 Troubleshooting
|
|
||||||
|
|
||||||
### Service Shows 🔴 Unhealthy
|
|
||||||
1. Click container → Logs
|
|
||||||
2. Look for errors
|
|
||||||
3. Check health check output in Inspect tab
|
|
||||||
4. Restart if needed
|
|
||||||
|
|
||||||
### Can't Access Portainer
|
|
||||||
```bash
|
|
||||||
docker-compose restart portainer
|
|
||||||
# Wait 30 seconds
|
|
||||||
# Try again at http://localhost:9000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Won't Start
|
|
||||||
1. Check Portainer logs for the service
|
|
||||||
2. Look for dependency issues (red containers)
|
|
||||||
3. Check resource limits (Stats tab)
|
|
||||||
4. Verify environment variables in Container → Env
|
|
||||||
|
|
||||||
## 📋 Health Check Endpoints
|
|
||||||
|
|
||||||
Test manually if needed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Web App
|
|
||||||
curl http://localhost/
|
|
||||||
|
|
||||||
# BookStack
|
|
||||||
curl http://localhost:6875/
|
|
||||||
|
|
||||||
# Matrix Synapse
|
|
||||||
curl http://localhost:8008/health
|
|
||||||
|
|
||||||
# Ollama
|
|
||||||
curl http://localhost:11434/api/tags
|
|
||||||
|
|
||||||
# Open WebUI
|
|
||||||
curl http://localhost:8080/
|
|
||||||
|
|
||||||
# Portainer
|
|
||||||
curl http://localhost:9000/api/system/status
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🏷️ Labels in Portainer
|
|
||||||
|
|
||||||
All services are tagged with:
|
|
||||||
- **description**: What it does
|
|
||||||
- **maintainer**: Studio EinsZwoVier
|
|
||||||
- **watchtower.enable**: Auto-update enabled
|
|
||||||
|
|
||||||
Filter by labels in Portainer UI!
|
|
||||||
|
|
||||||
## 🔄 Auto-Updates (Watchtower)
|
|
||||||
|
|
||||||
- **Schedule**: Every 24 hours
|
|
||||||
- **Action**: Pulls latest images and recreates containers
|
|
||||||
- **Which services**: Only those with `watchtower.enable=true` label
|
|
||||||
- **View updates**: Containers → watchtower → Logs
|
|
||||||
|
|
||||||
### Disable Auto-Update for a Service
|
|
||||||
Edit docker-compose.yml, remove:
|
|
||||||
```yaml
|
|
||||||
labels:
|
|
||||||
- "com.centurylinklabs.watchtower.enable=true"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📈 Resource Monitoring
|
|
||||||
|
|
||||||
**Portainer**: Container → Stats
|
|
||||||
Shows:
|
|
||||||
- CPU usage (%)
|
|
||||||
- Memory usage (MB / GB)
|
|
||||||
- Network I/O
|
|
||||||
- Block I/O
|
|
||||||
|
|
||||||
**Limits Set**:
|
|
||||||
- web: 1GB RAM, 1 CPU
|
|
||||||
- ollama: 8GB RAM, 4 CPU
|
|
||||||
|
|
||||||
## 🌐 Network: einszwovier_network
|
|
||||||
|
|
||||||
All services communicate on this network.
|
|
||||||
|
|
||||||
**View**: Networks → einszwovier_network → Connected containers
|
|
||||||
|
|
||||||
## 💾 Volumes
|
|
||||||
|
|
||||||
| Volume | Used By | Purpose |
|
|
||||||
|--------|---------|---------|
|
|
||||||
| portainer_data | portainer | Portainer config |
|
|
||||||
| ./data/uploads | web | PDF uploads |
|
|
||||||
| ./bookstack/* | bookstack | Wiki data |
|
|
||||||
| ./matrix/data | synapse | Matrix data |
|
|
||||||
| ./ollama | ollama | LLM models |
|
|
||||||
| ./open-webui | open-webui | Chat history |
|
|
||||||
|
|
||||||
**Backup**: See `backup.sh` script
|
|
||||||
|
|
||||||
## ⚙️ Useful Portainer Features
|
|
||||||
|
|
||||||
### Execute Shell Commands
|
|
||||||
1. Containers → [service] → Console
|
|
||||||
2. Select `/bin/bash` or `/bin/sh`
|
|
||||||
3. Click Connect
|
|
||||||
4. Run commands
|
|
||||||
|
|
||||||
### View Container Config
|
|
||||||
1. Containers → [service] → Inspect
|
|
||||||
2. See full JSON configuration
|
|
||||||
3. Copy environment variables
|
|
||||||
4. Check volume mounts
|
|
||||||
|
|
||||||
### Duplicate Container
|
|
||||||
1. Containers → [service] → Duplicate/Edit
|
|
||||||
2. Modify settings
|
|
||||||
3. Deploy as new container
|
|
||||||
|
|
||||||
### Container Template
|
|
||||||
1. App Templates → Custom Templates
|
|
||||||
2. Create from existing container
|
|
||||||
3. Reuse configuration
|
|
||||||
|
|
||||||
## 🎨 Status Icons
|
|
||||||
|
|
||||||
| Icon | Meaning | Action |
|
|
||||||
|------|---------|--------|
|
|
||||||
| 🟢 | Healthy | None needed |
|
|
||||||
| 🟡 | Starting | Wait (within start_period) |
|
|
||||||
| 🔴 | Unhealthy | Check logs |
|
|
||||||
| ⚫ | Stopped | Start container |
|
|
||||||
| 🔄 | Restarting | Wait or investigate |
|
|
||||||
|
|
||||||
## 📞 Quick Help
|
|
||||||
|
|
||||||
### "I can't find my containers!"
|
|
||||||
- Go to **Home**
|
|
||||||
- Select **Local** environment
|
|
||||||
- Go to **Stacks** → studio-einszwovier
|
|
||||||
|
|
||||||
### "Health checks keep failing"
|
|
||||||
- Increase `start_period` in docker-compose.yml
|
|
||||||
- Check service logs for actual errors
|
|
||||||
- Verify network connectivity
|
|
||||||
|
|
||||||
### "Portainer shows wrong status"
|
|
||||||
- Refresh page (F5)
|
|
||||||
- Check "Last Updated" timestamp
|
|
||||||
- Restart Portainer if stale
|
|
||||||
|
|
||||||
## 🔐 Security Notes
|
|
||||||
|
|
||||||
- Portainer admin password is set on first login (save it!)
|
|
||||||
- Docker socket mounted = full control (use carefully)
|
|
||||||
- HTTPS available on port 9443 (configure in Portainer settings)
|
|
||||||
|
|
||||||
## 📱 Mobile Access
|
|
||||||
|
|
||||||
Portainer works great on mobile:
|
|
||||||
1. Open browser on phone
|
|
||||||
2. Navigate to `http://[server-ip]:9000`
|
|
||||||
3. Same features as desktop!
|
|
||||||
|
|
||||||
## ✅ Daily Checklist
|
|
||||||
|
|
||||||
- [ ] All services showing 🟢 healthy
|
|
||||||
- [ ] No unusual CPU/memory spikes
|
|
||||||
- [ ] Recent watchtower update logs look good
|
|
||||||
- [ ] No red error logs in critical services
|
|
||||||
|
|
||||||
**Portainer makes this a 30-second check!**
|
|
||||||
66
README.md
66
README.md
|
|
@ -11,17 +11,21 @@
|
||||||
## 🎯 Features
|
## 🎯 Features
|
||||||
|
|
||||||
### Core Application
|
### Core Application
|
||||||
|
|
||||||
- **📄 PDF Cost Calculator**: Automated ink coverage analysis with color/B&W detection
|
- **📄 PDF Cost Calculator**: Automated ink coverage analysis with color/B&W detection
|
||||||
- **🖼️ Course Management**: Dynamic course list with image gallery and modal viewer
|
- **🖼️ Course Management**: Dynamic course list with image gallery and modal viewer
|
||||||
- **📊 Cost Estimation**: Per-page breakdown with configurable rates (€/m²)
|
- **📊 Cost Estimation**: Per-page breakdown with configurable rates (€/m²)
|
||||||
- **💬 Matrix Integration**: Automatic order submission to Matrix room
|
- **💬 Matrix Integration**: Automatic order submission to Matrix room
|
||||||
|
|
||||||
### Integrated Services
|
### Integrated Services
|
||||||
|
|
||||||
- **📚 BookStack Wiki**: Documentation and knowledge base (port 6875)
|
- **📚 BookStack Wiki**: Documentation and knowledge base (port 6875)
|
||||||
- **🤖 Ollama + Open WebUI**: Local LLM chatbot interface (port 8080)
|
- **🤖 Ollama + Open WebUI**: Local LLM chatbot interface (port 8080)
|
||||||
- **📨 Matrix Synapse**: Print order collection and payment tracking (port 8008)
|
- **📨 Matrix Synapse**: Print order collection and payment tracking (port 8008)
|
||||||
- **🐳 Portainer**: Container management dashboard (port 9000)
|
- **🐳 Portainer**: Container management dashboard (port 9000)
|
||||||
- **🔄 Watchtower**: Automatic container updates every 24 hours
|
- **<EFBFBD> JupyterHub**: Multi-user Jupyter notebook server for drone programming (port 8001)
|
||||||
|
- **🦊 Forgejo**: Self-hosted Git service for code collaboration (port 3003)
|
||||||
|
- **<EFBFBD>🔄 Watchtower**: Automatic container updates every 24 hours
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -74,35 +78,55 @@ graph TB
|
||||||
OpenWebUI[💭 Open WebUI<br/>:8080]
|
OpenWebUI[💭 Open WebUI<br/>:8080]
|
||||||
Portainer[🐳 Portainer<br/>:9000]
|
Portainer[🐳 Portainer<br/>:9000]
|
||||||
Synapse[📨 Matrix Synapse<br/>:8008]
|
Synapse[📨 Matrix Synapse<br/>:8008]
|
||||||
|
JupyterHub[📓 JupyterHub<br/>:8001<br/>Drone Programming]
|
||||||
|
Forgejo[🦊 Forgejo Git<br/>:3003<br/>Code Collaboration]
|
||||||
|
|
||||||
LP -.-> BookStack
|
LP -.-> BookStack
|
||||||
LP -.-> OpenWebUI
|
LP -.-> OpenWebUI
|
||||||
LP -.-> Portainer
|
LP -.-> Portainer
|
||||||
|
LP -.-> JupyterHub
|
||||||
|
LP -.-> Forgejo
|
||||||
OpenWebUI --> Ollama
|
OpenWebUI --> Ollama
|
||||||
MatrixRoom --> Synapse
|
MatrixRoom --> Synapse
|
||||||
end
|
end
|
||||||
|
|
||||||
|
subgraph "Educational Stack"
|
||||||
|
Notebooks[📒 Jupyter Notebooks<br/>Python Drone Code]
|
||||||
|
GitRepos[📦 Git Repositories<br/>Project Source Code]
|
||||||
|
DronePackages[🚁 djitellopy<br/>Tello SDK]
|
||||||
|
|
||||||
|
JupyterHub --> Notebooks
|
||||||
|
Forgejo --> GitRepos
|
||||||
|
Notebooks -.-> DronePackages
|
||||||
|
end
|
||||||
|
|
||||||
subgraph "Data Persistence"
|
subgraph "Data Persistence"
|
||||||
Uploads[📁 data/uploads/<br/>PDF Files]
|
Uploads[📁 data/uploads/<br/>PDF Files]
|
||||||
Courses[📋 data/courses.csv<br/>Course List]
|
Courses[📋 data/courses.csv<br/>Course List]
|
||||||
MatrixDB[(🗄️ Matrix DB<br/>homeserver.db)]
|
MatrixDB[(🗄️ Matrix DB<br/>homeserver.db)]
|
||||||
BookDB[(🗄️ BookStack DB<br/>MariaDB)]
|
BookDB[(🗄️ BookStack DB<br/>MariaDB)]
|
||||||
|
JupyterVolumes[(💾 Jupyter Volumes<br/>User Workspaces)]
|
||||||
|
ForgejoData[(📚 Forgejo Data<br/>Git Repos)]
|
||||||
|
|
||||||
Upload --> Uploads
|
Upload --> Uploads
|
||||||
CSV --> Courses
|
CSV --> Courses
|
||||||
Synapse --> MatrixDB
|
Synapse --> MatrixDB
|
||||||
BookStack --> BookDB
|
BookStack --> BookDB
|
||||||
|
JupyterHub --> JupyterVolumes
|
||||||
|
Forgejo --> ForgejoData
|
||||||
end
|
end
|
||||||
|
|
||||||
classDef webapp fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
|
classDef webapp fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
|
||||||
classDef service fill:#fff3e0,stroke:#f57c00,stroke-width:2px
|
classDef service fill:#fff3e0,stroke:#f57c00,stroke-width:2px
|
||||||
classDef data fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
|
classDef data fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
|
||||||
classDef matrix fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
|
classDef matrix fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
|
||||||
|
classDef edu fill:#fce4ec,stroke:#c2185b,stroke-width:2px
|
||||||
|
|
||||||
class LP,About,Cost,Upload,Analysis,Quote webapp
|
class LP,About,Cost,Upload,Analysis,Quote webapp
|
||||||
class BookStack,Ollama,OpenWebUI,Portainer,Synapse service
|
class BookStack,Ollama,OpenWebUI,Portainer,Synapse,JupyterHub,Forgejo service
|
||||||
class Uploads,Courses,MatrixDB,BookDB data
|
class Uploads,Courses,MatrixDB,BookDB,JupyterVolumes,ForgejoData data
|
||||||
class MatrixRoom,PDF_Upload,Summary,CSV,Images,Modal matrix
|
class MatrixRoom,PDF_Upload,Summary,CSV,Images,Modal matrix
|
||||||
|
class Notebooks,GitRepos,DronePackages edu
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -113,47 +137,55 @@ graph TB
|
||||||
|
|
||||||
- **Docker** & **Docker Compose** installed
|
- **Docker** & **Docker Compose** installed
|
||||||
- **Poppler** (for pdf2image - included in Docker)
|
- **Poppler** (for pdf2image - included in Docker)
|
||||||
- **Port availability**: 80, 8008, 8080, 6875, 9000, 11434
|
- **Port availability**: 80, 3003, 6875, 8001, 8008, 8080, 9000, 11434
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
1. **Clone the repository:**
|
1. **Clone the repository:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/arontaupe/124-webapp.git
|
git clone https://github.com/arontaupe/124-webapp.git
|
||||||
cd 124-webapp
|
cd 124-webapp
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Configure environment:**
|
2. **Configure environment:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
nano .env # Update SERVER_HOSTNAME and passwords
|
nano .env # Update SERVER_HOSTNAME and passwords
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Start the stack:**
|
3. **Start the stack:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d --build
|
docker compose up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Access services:**
|
4. **Access services:**
|
||||||
- Web App: http://localhost (or http://your-server)
|
- Web App: <http://localhost> (or <http://your-server>)
|
||||||
- BookStack: http://localhost:6875
|
- BookStack: <http://localhost:6875>
|
||||||
- Open WebUI: http://localhost:8080
|
- Open WebUI: <http://localhost:8080>
|
||||||
- Portainer: http://localhost:9000
|
- Portainer: <http://localhost:9000>
|
||||||
- Matrix: http://localhost:8008
|
- Matrix: <http://localhost:8008>
|
||||||
|
- JupyterHub: <http://localhost:8001>
|
||||||
|
- Forgejo: <http://localhost:3003>
|
||||||
|
|
||||||
### First-Time Setup
|
### First-Time Setup
|
||||||
|
|
||||||
1. **Get Matrix Room ID:**
|
1. **Get Matrix Room ID:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python get_room_id.py
|
python get_room_id.py
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Update .env with room ID:**
|
2. **Update .env with room ID:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
MATRIX_ROOM="!YourRoomID:${SERVER_HOSTNAME}"
|
MATRIX_ROOM="!YourRoomID:${SERVER_HOSTNAME}"
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Restart web container:**
|
3. **Restart web container:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose restart web
|
docker compose restart web
|
||||||
```
|
```
|
||||||
|
|
@ -167,12 +199,14 @@ Courses are managed via CSV file with optional images:
|
||||||
### Add a Course
|
### Add a Course
|
||||||
|
|
||||||
1. **Edit `data/courses.csv`:**
|
1. **Edit `data/courses.csv`:**
|
||||||
|
|
||||||
```csv
|
```csv
|
||||||
title,description,dates,offen_fuer,image
|
title,description,dates,offen_fuer,image
|
||||||
My Course,Learn cool stuff,"Oct '25",Grade 10,/static/images/courses/my-course.jpg
|
My Course,Learn cool stuff,"Oct '25",Grade 10,/static/images/courses/my-course.jpg
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Add course image (optional):**
|
2. **Add course image (optional):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp my-image.jpg static/images/courses/my-course.jpg
|
cp my-image.jpg static/images/courses/my-course.jpg
|
||||||
```
|
```
|
||||||
|
|
@ -180,6 +214,7 @@ Courses are managed via CSV file with optional images:
|
||||||
3. **Changes apply immediately** (no restart needed)
|
3. **Changes apply immediately** (no restart needed)
|
||||||
|
|
||||||
### Course Image Features
|
### Course Image Features
|
||||||
|
|
||||||
- **Thumbnail view**: 80x80px next to course info
|
- **Thumbnail view**: 80x80px next to course info
|
||||||
- **Hover zoom**: Expands to 200x200px on hover
|
- **Hover zoom**: Expands to 200x200px on hover
|
||||||
- **Click to enlarge**: Opens full-size modal viewer
|
- **Click to enlarge**: Opens full-size modal viewer
|
||||||
|
|
@ -215,6 +250,11 @@ OLLAMA_PORT=11434
|
||||||
OPENWEBUI_PORT=8080
|
OPENWEBUI_PORT=8080
|
||||||
BOOKSTACK_PORT=6875
|
BOOKSTACK_PORT=6875
|
||||||
PORTAINER_PORT=9000
|
PORTAINER_PORT=9000
|
||||||
|
JUPYTER_PORT=8001
|
||||||
|
FORGEJO_PORT=3003
|
||||||
|
|
||||||
|
# JupyterHub Authentication
|
||||||
|
JUPYTER_PASSWORD=einszwo4
|
||||||
```
|
```
|
||||||
|
|
||||||
See [.env.example](.env.example) for full configuration.
|
See [.env.example](.env.example) for full configuration.
|
||||||
|
|
@ -231,12 +271,15 @@ See [.env.example](.env.example) for full configuration.
|
||||||
| **open-webui** | ghcr.io/open-webui/open-webui | 8080 | LLM chat UI | 2 CPU, 2GB RAM |
|
| **open-webui** | ghcr.io/open-webui/open-webui | 8080 | LLM chat UI | 2 CPU, 2GB RAM |
|
||||||
| **bookstack** | lscr.io/linuxserver/bookstack | 6875 | Documentation wiki | Default |
|
| **bookstack** | lscr.io/linuxserver/bookstack | 6875 | Documentation wiki | Default |
|
||||||
| **bookstack-mariadb** | lscr.io/linuxserver/mariadb | 3306 | Database for BookStack | Default |
|
| **bookstack-mariadb** | lscr.io/linuxserver/mariadb | 3306 | Database for BookStack | Default |
|
||||||
|
| **jupyterhub** | Custom (Python 3.13) | 8001 | Multi-user Jupyter notebooks | 2 CPU, 2GB RAM |
|
||||||
|
| **forgejo** | codeberg.org/forgejo/forgejo:11 | 3003, 222 | Git service (web, SSH) | Default |
|
||||||
| **portainer** | portainer/portainer-ce | 9000, 9443 | Container management | Default |
|
| **portainer** | portainer/portainer-ce | 9000, 9443 | Container management | Default |
|
||||||
| **watchtower** | containrrr/watchtower | - | Auto-updates (24h) | Default |
|
| **watchtower** | containrrr/watchtower | - | Auto-updates (24h) | Default |
|
||||||
|
|
||||||
### Health Checks
|
### Health Checks
|
||||||
|
|
||||||
All services have health checks configured:
|
All services have health checks configured:
|
||||||
|
|
||||||
- **web**: `curl http://localhost:8000/`
|
- **web**: `curl http://localhost:8000/`
|
||||||
- **synapse**: `curl http://localhost:8008/_matrix/static/`
|
- **synapse**: `curl http://localhost:8008/_matrix/static/`
|
||||||
- **ollama**: `curl http://localhost:11434/`
|
- **ollama**: `curl http://localhost:11434/`
|
||||||
|
|
@ -283,6 +326,7 @@ See [SECURITY.md](SECURITY.md) for comprehensive security guidelines.
|
||||||
```
|
```
|
||||||
|
|
||||||
Creates timestamped backups of:
|
Creates timestamped backups of:
|
||||||
|
|
||||||
- BookStack database & files
|
- BookStack database & files
|
||||||
- Matrix homeserver data
|
- Matrix homeserver data
|
||||||
- PDF uploads
|
- PDF uploads
|
||||||
|
|
@ -302,11 +346,13 @@ Creates timestamped backups of:
|
||||||
This application is designed to be **fully portable**. To move to a new server:
|
This application is designed to be **fully portable**. To move to a new server:
|
||||||
|
|
||||||
1. **On old server:**
|
1. **On old server:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./backup.sh
|
./backup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **On new server:**
|
2. **On new server:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone <repo-url>
|
git clone <repo-url>
|
||||||
cd 124-webapp
|
cd 124-webapp
|
||||||
|
|
@ -393,7 +439,7 @@ This project is part of Studio EinsZwoVier educational initiative.
|
||||||
Gabriele-von-Bülow-Gymnasium
|
Gabriele-von-Bülow-Gymnasium
|
||||||
Tile-Brügge-Weg 63, 13509 Berlin (Tegel)
|
Tile-Brügge-Weg 63, 13509 Berlin (Tegel)
|
||||||
|
|
||||||
- **Email**: einszwovier@gvb-gymnasium.de
|
- **Email**: <einszwovier@gvb-gymnasium.de>
|
||||||
- **Team**: Aron Petau & Friedrich Weber
|
- **Team**: Aron Petau & Friedrich Weber
|
||||||
- **Hours**: Tuesday - Thursday, 11:00 - 16:00
|
- **Hours**: Tuesday - Thursday, 11:00 - 16:00
|
||||||
- **Location**: Room 124
|
- **Location**: Room 124
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ echo "Working directory: $SCRIPT_DIR"
|
||||||
# 1. Backup BookStack database
|
# 1. Backup BookStack database
|
||||||
echo "Backing up BookStack database..."
|
echo "Backing up BookStack database..."
|
||||||
docker exec bookstack-mariadb mariadb-dump \
|
docker exec bookstack-mariadb mariadb-dump \
|
||||||
-u"${DB_USERNAME:-bookstack}" \
|
-u"${BOOKSTACK_DB_USERNAME:-bookstack}" \
|
||||||
-p"${DB_PASSWORD}" \
|
-p"${BOOKSTACK_DB_PASSWORD}" \
|
||||||
"${DB_DATABASE:-bookstack}" \
|
"${BOOKSTACK_DB_DATABASE:-bookstack}" \
|
||||||
| gzip > "$BACKUP_DIR/bookstack_db_$DATE.sql.gz"
|
| gzip > "$BACKUP_DIR/bookstack_db_$DATE.sql.gz"
|
||||||
|
|
||||||
# 2. Backup BookStack uploads and config
|
# 2. Backup BookStack uploads and config
|
||||||
|
|
|
||||||
BIN
data/.DS_Store
vendored
BIN
data/.DS_Store
vendored
Binary file not shown.
177
data/Tello_Drone_Setup.ipynb
Normal file
177
data/Tello_Drone_Setup.ipynb
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "49a243e8",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Tello Drone Programming - Setup\n",
|
||||||
|
"\n",
|
||||||
|
"Dieses Notebook hilft dir, die Programmierung des DJI Tello Drohne einzurichten.\n",
|
||||||
|
"\n",
|
||||||
|
"## Schritt 1: Pakete installieren\n",
|
||||||
|
"\n",
|
||||||
|
"Führe die folgende Zelle aus, um die benötigten Python-Pakete zu installieren:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "4804023e",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Installiere djitellopy und Abhängigkeiten\n",
|
||||||
|
"!pip install djitellopy opencv-python pillow"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "a480f600",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Schritt 2: Import und Verbindungstest\n",
|
||||||
|
"\n",
|
||||||
|
"Nach der Installation, importiere die Bibliothek:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "7bcda678",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"from djitellopy import Tello\n",
|
||||||
|
"import time\n",
|
||||||
|
"\n",
|
||||||
|
"print(\"✅ djitellopy erfolgreich importiert!\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "165d7797",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Schritt 3: Drohne verbinden (nur ausführen wenn Drohne eingeschaltet ist!)\n",
|
||||||
|
"\n",
|
||||||
|
"**⚠️ WICHTIG:** Stelle sicher, dass:\n",
|
||||||
|
"1. Die Tello Drohne eingeschaltet ist\n",
|
||||||
|
"2. Dein Computer mit dem WiFi der Drohne verbunden ist (TELLO-XXXXXX)\n",
|
||||||
|
"3. Du genug Platz hast (mindestens 2x2 Meter freier Raum)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "6acedf15",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Drohne initialisieren\n",
|
||||||
|
"drone = Tello()\n",
|
||||||
|
"\n",
|
||||||
|
"# Verbindung aufbauen\n",
|
||||||
|
"drone.connect()\n",
|
||||||
|
"\n",
|
||||||
|
"# Batterie-Status prüfen\n",
|
||||||
|
"battery = drone.get_battery()\n",
|
||||||
|
"print(f\"🔋 Batterie: {battery}%\")\n",
|
||||||
|
"\n",
|
||||||
|
"if battery < 20:\n",
|
||||||
|
" print(\"⚠️ Warnung: Batterie niedrig! Bitte aufladen.\")\n",
|
||||||
|
"else:\n",
|
||||||
|
" print(\"✅ Drohne bereit!\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "c4c90848",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Schritt 4: Einfacher Testflug\n",
|
||||||
|
"\n",
|
||||||
|
"**⚠️ SICHERHEIT ZUERST:**\n",
|
||||||
|
"- Freier Raum um dich herum\n",
|
||||||
|
"- Keine Personen in der Nähe\n",
|
||||||
|
"- Drohne auf ebener Fläche\n",
|
||||||
|
"- Bereit, die Drohne zu fangen falls nötig"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "3ce22798",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Starten und einfache Bewegung\n",
|
||||||
|
"drone.takeoff() # Abheben\n",
|
||||||
|
"time.sleep(3) # 3 Sekunden schweben\n",
|
||||||
|
"\n",
|
||||||
|
"drone.move_up(30) # 30cm nach oben\n",
|
||||||
|
"time.sleep(2)\n",
|
||||||
|
"\n",
|
||||||
|
"drone.rotate_clockwise(90) # 90° drehen\n",
|
||||||
|
"time.sleep(2)\n",
|
||||||
|
"\n",
|
||||||
|
"drone.land() # Landen\n",
|
||||||
|
"print(\"✅ Testflug abgeschlossen!\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "3b616df2",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Nützliche Befehle\n",
|
||||||
|
"\n",
|
||||||
|
"Hier sind einige grundlegende Befehle:\n",
|
||||||
|
"\n",
|
||||||
|
"```python\n",
|
||||||
|
"# Bewegungen\n",
|
||||||
|
"drone.takeoff() # Abheben\n",
|
||||||
|
"drone.land() # Landen\n",
|
||||||
|
"drone.move_up(x) # x cm nach oben\n",
|
||||||
|
"drone.move_down(x) # x cm nach unten\n",
|
||||||
|
"drone.move_forward(x) # x cm vorwärts\n",
|
||||||
|
"drone.move_back(x) # x cm rückwärts\n",
|
||||||
|
"drone.move_left(x) # x cm links\n",
|
||||||
|
"drone.move_right(x) # x cm rechts\n",
|
||||||
|
"drone.rotate_clockwise(x) # x° im Uhrzeigersinn\n",
|
||||||
|
"drone.rotate_counter_clockwise(x) # x° gegen Uhrzeigersinn\n",
|
||||||
|
"\n",
|
||||||
|
"# Informationen\n",
|
||||||
|
"drone.get_battery() # Batterie-Status\n",
|
||||||
|
"drone.get_height() # Aktuelle Höhe\n",
|
||||||
|
"drone.get_speed() # Geschwindigkeit\n",
|
||||||
|
"\n",
|
||||||
|
"# Notfall\n",
|
||||||
|
"drone.emergency() # NOTLANDUNG (sofort stoppen!)\n",
|
||||||
|
"```"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "441ebe1d",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Weiterführende Ressourcen\n",
|
||||||
|
"\n",
|
||||||
|
"- [DJITelloPy Dokumentation](https://djitellopy.readthedocs.io/)\n",
|
||||||
|
"- [Beispiele auf GitHub](https://github.com/damiafuentes/DJITelloPy/tree/master/examples)\n",
|
||||||
|
"\n",
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"**Viel Spaß beim Programmieren! 🚁**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"language_info": {
|
||||||
|
"name": "python"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
|
|
@ -108,10 +108,10 @@ services:
|
||||||
image: lscr.io/linuxserver/bookstack:latest
|
image: lscr.io/linuxserver/bookstack:latest
|
||||||
container_name: bookstack
|
container_name: bookstack
|
||||||
environment:
|
environment:
|
||||||
- APP_KEY=${BOOKSTACK_APP_KEY}
|
- MYSQL_ROOT_PASSWORD=${BOOKSTACK_DB_PASSWORD}
|
||||||
- APP_URL=${BOOKSTACK_APP_URL}
|
- MYSQL_DATABASE=${BOOKSTACK_DB_DATABASE}
|
||||||
env_file:
|
- MYSQL_USER=${BOOKSTACK_DB_USERNAME}
|
||||||
- .env
|
- MYSQL_PASSWORD=${BOOKSTACK_DB_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
- ./bookstack/bookstack_app_data:/config
|
- ./bookstack/bookstack_app_data:/config
|
||||||
ports:
|
ports:
|
||||||
|
|
@ -121,7 +121,11 @@ services:
|
||||||
bookstack-mariadb:
|
bookstack-mariadb:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
"mariadb -u${BOOKSTACK_DB_USERNAME} -p${BOOKSTACK_DB_PASSWORD} -e 'SELECT 1' || exit 1",
|
||||||
|
]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
@ -135,10 +139,10 @@ services:
|
||||||
image: lscr.io/linuxserver/mariadb:latest
|
image: lscr.io/linuxserver/mariadb:latest
|
||||||
container_name: bookstack-mariadb
|
container_name: bookstack-mariadb
|
||||||
environment:
|
environment:
|
||||||
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
|
- MYSQL_ROOT_PASSWORD=${BOOKSTACK_DB_PASSWORD}
|
||||||
- MYSQL_DATABASE=${DB_DATABASE}
|
- MYSQL_DATABASE=${BOOKSTACK_DB_DATABASE}
|
||||||
- MYSQL_USER=${DB_USERNAME}
|
- MYSQL_USER=${BOOKSTACK_DB_USERNAME}
|
||||||
- MYSQL_PASSWORD=${DB_PASSWORD}
|
- MYSQL_PASSWORD=${BOOKSTACK_DB_PASSWORD}
|
||||||
- PUID=1000
|
- PUID=1000
|
||||||
- PGID=1000
|
- PGID=1000
|
||||||
- TZ=Europe/Berlin
|
- TZ=Europe/Berlin
|
||||||
|
|
@ -151,7 +155,7 @@ services:
|
||||||
test:
|
test:
|
||||||
[
|
[
|
||||||
"CMD-SHELL",
|
"CMD-SHELL",
|
||||||
"mariadb -u${DB_USERNAME} -p${DB_PASSWORD} -e 'SELECT 1' || exit 1",
|
"mariadb -u${BOOKSTACK_DB_USERNAME} -p${BOOKSTACK_DB_PASSWORD} -e 'SELECT 1' || exit 1",
|
||||||
]
|
]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
|
|
@ -207,6 +211,59 @@ services:
|
||||||
- "description=Portainer Container Management UI"
|
- "description=Portainer Container Management UI"
|
||||||
- "maintainer=Studio EinsZwoVier"
|
- "maintainer=Studio EinsZwoVier"
|
||||||
|
|
||||||
|
jupyterhub:
|
||||||
|
build: ./jupyterhub
|
||||||
|
container_name: jupyterhub
|
||||||
|
ports:
|
||||||
|
- "8001:8001"
|
||||||
|
- "8081:8081"
|
||||||
|
volumes:
|
||||||
|
- ./data:/home
|
||||||
|
- ./jupyterhub/jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py:ro
|
||||||
|
- ./static/images/logo.png:/srv/jupyterhub/logo.png:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock # Docker socket for spawning containers
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- JUPYTERHUB_SINGLEUSER_APP=jupyterlab
|
||||||
|
restart: unless-stopped
|
||||||
|
mem_limit: 2g
|
||||||
|
cpus: 1.0
|
||||||
|
depends_on:
|
||||||
|
- web
|
||||||
|
labels:
|
||||||
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
|
- "description=JupyterHub for interactive notebooks"
|
||||||
|
- "maintainer=Studio EinsZwoVier"
|
||||||
|
|
||||||
|
forgejo:
|
||||||
|
image: codeberg.org/forgejo/forgejo:11
|
||||||
|
container_name: forgejo
|
||||||
|
environment:
|
||||||
|
- USER_UID=1000
|
||||||
|
- USER_GID=1000
|
||||||
|
- TZ=Europe/Berlin
|
||||||
|
- FORGEJO__repository__ENABLE_PUSH_CREATE_USER=true
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${FORGEJO_PORT:-3003}:3000"
|
||||||
|
- "222:22"
|
||||||
|
volumes:
|
||||||
|
- ./forgejo/data:/data
|
||||||
|
mem_limit: 2g
|
||||||
|
cpus: 2.0
|
||||||
|
mem_reservation: 512m
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:3000/ || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
labels:
|
||||||
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
|
- "description=Forgejo Git Server"
|
||||||
|
- "maintainer=Studio EinsZwoVier"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
portainer_data:
|
portainer_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
|
||||||
1
forgejo/data/git/.ssh/environment
Normal file
1
forgejo/data/git/.ssh/environment
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
GITEA_CUSTOM=/data/gitea
|
||||||
63
forgejo/data/gitea/conf/app.ini
Normal file
63
forgejo/data/gitea/conf/app.ini
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
APP_NAME = Forgejo: Beyond coding. We forge.
|
||||||
|
RUN_MODE = prod
|
||||||
|
|
||||||
|
[repository]
|
||||||
|
ROOT = /data/git/repositories
|
||||||
|
ENABLE_PUSH_CREATE_USER = true
|
||||||
|
|
||||||
|
[repository.local]
|
||||||
|
LOCAL_COPY_PATH = /data/gitea/tmp/local-repo
|
||||||
|
|
||||||
|
[repository.upload]
|
||||||
|
TEMP_PATH = /data/gitea/uploads
|
||||||
|
|
||||||
|
[server]
|
||||||
|
APP_DATA_PATH = /data/gitea
|
||||||
|
DOMAIN = localhost
|
||||||
|
SSH_DOMAIN = localhost
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
ROOT_URL =
|
||||||
|
DISABLE_SSH = false
|
||||||
|
SSH_PORT = 22
|
||||||
|
SSH_LISTEN_PORT = 22
|
||||||
|
LFS_START_SERVER = false
|
||||||
|
|
||||||
|
[database]
|
||||||
|
PATH = /data/gitea/gitea.db
|
||||||
|
DB_TYPE = sqlite3
|
||||||
|
HOST = localhost:3306
|
||||||
|
NAME = gitea
|
||||||
|
USER = root
|
||||||
|
PASSWD =
|
||||||
|
LOG_SQL = false
|
||||||
|
|
||||||
|
[indexer]
|
||||||
|
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
|
||||||
|
|
||||||
|
[session]
|
||||||
|
PROVIDER_CONFIG = /data/gitea/sessions
|
||||||
|
|
||||||
|
[picture]
|
||||||
|
AVATAR_UPLOAD_PATH = /data/gitea/avatars
|
||||||
|
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
|
||||||
|
|
||||||
|
[attachment]
|
||||||
|
PATH = /data/gitea/attachments
|
||||||
|
|
||||||
|
[log]
|
||||||
|
MODE = console
|
||||||
|
LEVEL = info
|
||||||
|
ROOT_PATH = /data/gitea/log
|
||||||
|
|
||||||
|
[security]
|
||||||
|
INSTALL_LOCK = false
|
||||||
|
SECRET_KEY =
|
||||||
|
REVERSE_PROXY_LIMIT = 1
|
||||||
|
REVERSE_PROXY_TRUSTED_PROXIES = *
|
||||||
|
|
||||||
|
[service]
|
||||||
|
DISABLE_REGISTRATION = false
|
||||||
|
REQUIRE_SIGNIN_VIEW = false
|
||||||
|
|
||||||
|
[lfs]
|
||||||
|
PATH = /data/git/lfs
|
||||||
9
forgejo/data/ssh/ssh_host_ecdsa_key
Normal file
9
forgejo/data/ssh/ssh_host_ecdsa_key
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
|
||||||
|
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQ02EGaaw86ZKkpWSL8gktKug0A/XNF
|
||||||
|
TgcY469G/5ecX9H/AMxrPlOKCU0K+3eWGSCISQGlVLBRNpHK5VM9ga13AAAAsJrl+aya5f
|
||||||
|
msAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDTYQZprDzpkqSlZ
|
||||||
|
IvyCS0q6DQD9c0VOBxjjr0b/l5xf0f8AzGs+U4oJTQr7d5YZIIhJAaVUsFE2kcrlUz2BrX
|
||||||
|
cAAAAhAPrnsLBtojpOaaAt0yDgy/lCpqRnfbGBkV5FMiFYvm+fAAAAEXJvb3RAMmQ4Yjk4
|
||||||
|
Nzg0OWMyAQIDBAUG
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
1
forgejo/data/ssh/ssh_host_ecdsa_key.pub
Normal file
1
forgejo/data/ssh/ssh_host_ecdsa_key.pub
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDTYQZprDzpkqSlZIvyCS0q6DQD9c0VOBxjjr0b/l5xf0f8AzGs+U4oJTQr7d5YZIIhJAaVUsFE2kcrlUz2BrXc= root@2d8b987849c2
|
||||||
7
forgejo/data/ssh/ssh_host_ed25519_key
Normal file
7
forgejo/data/ssh/ssh_host_ed25519_key
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACD7RYGr6qRspA4rgtvxhXJiS4a7z/hXm8q2XS9J8iqncQAAAJit3hcbrd4X
|
||||||
|
GwAAAAtzc2gtZWQyNTUxOQAAACD7RYGr6qRspA4rgtvxhXJiS4a7z/hXm8q2XS9J8iqncQ
|
||||||
|
AAAEB2I1zsRzNSHt6lqkavsXdj1fi2cDcnSCImC4Kpaqi3APtFgavqpGykDiuC2/GFcmJL
|
||||||
|
hrvP+FebyrZdL0nyKqdxAAAAEXJvb3RAMmQ4Yjk4Nzg0OWMyAQIDBA==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
1
forgejo/data/ssh/ssh_host_ed25519_key.pub
Normal file
1
forgejo/data/ssh/ssh_host_ed25519_key.pub
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPtFgavqpGykDiuC2/GFcmJLhrvP+FebyrZdL0nyKqdx root@2d8b987849c2
|
||||||
38
forgejo/data/ssh/ssh_host_rsa_key
Normal file
38
forgejo/data/ssh/ssh_host_rsa_key
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
1
forgejo/data/ssh/ssh_host_rsa_key.pub
Normal file
1
forgejo/data/ssh/ssh_host_rsa_key.pub
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2FfioI52Oa6bL480x/ZFQiJqS4F2pBcxEh/OkozGyNIy7F/TpiyphP59FHoQ52JtTMwcmX0GoD8bPDYi26FIb2ugpW+TBtijJN+m3q03qrEnKxMHrMsZQoZT+bO05WAUvlpO2qO+i7f5QSfQEi+heXrG7WC2MYli3ZC3+5PEpK3ey9wbhc7nnoTtHZW1Tu/2cnqu2AkLHnkirnTNGDHM73adyESet+wps6sbyr7qO/ieOmwGoSIelc2Kk/zBQdzAAMvpbE8FyzrK5qcJaDBDx5grLJCT+ugtxIPtXRhcEf4LGQ/0/1ntBd19qID6Dai7yUS47C1mxE2IWTkb2Xu2tk6D0T1kh9vhjIVUD31qQotAzi+kHpuHz7tKLfIxRjvD2YUjiy93ygnFOxofTW3RO1irbOLLYc7WPDBp5Ee3IPZUDu363ciViuF0w1BJO/v3VkfIpfFwJW/dm3SAheW8UtOf5expa33TCSy3tSKENQdhp4fgKRXVzv6fV3xQP75s= root@2d8b987849c2
|
||||||
28
jupyterhub/Dockerfile
Normal file
28
jupyterhub/Dockerfile
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
# Install system deps
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
nodejs npm \
|
||||||
|
git \
|
||||||
|
sudo \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install JupyterHub, JupyterLab, notebook (required for singleuser), and DockerSpawner
|
||||||
|
RUN pip install --no-cache-dir jupyterhub jupyterlab notebook dockerspawner
|
||||||
|
|
||||||
|
# Install configurable-http-proxy (required by JupyterHub)
|
||||||
|
RUN npm install -g configurable-http-proxy@^4.0.0
|
||||||
|
|
||||||
|
# Ensure npm global binaries are available in PATH
|
||||||
|
ENV PATH="/usr/local/bin:${PATH}"
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
RUN mkdir -p /srv/jupyterhub /srv/jupyterhub/data
|
||||||
|
WORKDIR /srv/jupyterhub
|
||||||
|
|
||||||
|
COPY jupyterhub_config.py /srv/jupyterhub/
|
||||||
|
|
||||||
|
EXPOSE 8001
|
||||||
|
|
||||||
|
CMD ["jupyterhub", "-f", "/srv/jupyterhub/jupyterhub_config.py"]
|
||||||
79
jupyterhub/jupyterhub_config.py
Normal file
79
jupyterhub/jupyterhub_config.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
from traitlets.config import get_config
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Minimal JupyterHub config for local multi-user usage inside docker-compose.
|
||||||
|
# This config is intentionally small — customize for auth, spawners, and TLS in production.
|
||||||
|
|
||||||
|
c = get_config()
|
||||||
|
|
||||||
|
# Hub internal URL (where the hub process listens for /hub requests)
|
||||||
|
c.JupyterHub.bind_url = 'http://:8081/hub/'
|
||||||
|
|
||||||
|
# Hub IP for spawned containers to connect back (use container name on Docker network)
|
||||||
|
c.JupyterHub.hub_connect_ip = 'jupyterhub'
|
||||||
|
|
||||||
|
# Proxy configuration
|
||||||
|
# - Proxy public address (what users access): default is port 8000, we want 8001
|
||||||
|
# - Proxy API address (hub talks to proxy control): separate port 8002
|
||||||
|
c.JupyterHub.port = 8001 # Public proxy port
|
||||||
|
c.ConfigurableHTTPProxy.api_url = 'http://127.0.0.1:8002'
|
||||||
|
|
||||||
|
# Lightweight built-in authenticator: no external package required. This is
|
||||||
|
# intended only for local development/testing. It accepts any username as long
|
||||||
|
# as the password equals the value of JUPYTER_PASSWORD (default: einszwo4).
|
||||||
|
from jupyterhub.auth import Authenticator
|
||||||
|
|
||||||
|
class SimpleEnvPasswordAuthenticator(Authenticator):
|
||||||
|
async def authenticate(self, handler, data):
|
||||||
|
"""Authenticate any username when the shared env password matches.
|
||||||
|
|
||||||
|
Dev-only: returns the username on success, otherwise None.
|
||||||
|
"""
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
if not username or not password:
|
||||||
|
return None
|
||||||
|
if password == os.environ.get('JUPYTER_PASSWORD', 'einszwo4'):
|
||||||
|
return username
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Use the in-file authenticator for dev/testing
|
||||||
|
c.JupyterHub.authenticator_class = SimpleEnvPasswordAuthenticator
|
||||||
|
|
||||||
|
# Allow any authenticated user to access (suppress the warning)
|
||||||
|
c.Authenticator.allow_all = True
|
||||||
|
|
||||||
|
# Use DockerSpawner for production-ready containerized user servers
|
||||||
|
from dockerspawner import DockerSpawner
|
||||||
|
c.JupyterHub.spawner_class = DockerSpawner
|
||||||
|
|
||||||
|
# Docker image for single-user notebook servers
|
||||||
|
c.DockerSpawner.image = 'jupyter/scipy-notebook:latest'
|
||||||
|
|
||||||
|
# Connect to Docker socket
|
||||||
|
c.DockerSpawner.use_internal_ip = True
|
||||||
|
c.DockerSpawner.network_name = 'einszwovier_network'
|
||||||
|
|
||||||
|
# Remove containers after they stop
|
||||||
|
c.DockerSpawner.remove = True
|
||||||
|
|
||||||
|
# Mount the data directory for persistent storage
|
||||||
|
# Use the data directory we already have mounted in docker-compose
|
||||||
|
c.DockerSpawner.volumes = {
|
||||||
|
'jupyterhub-user-{username}': {'bind': '/home/jovyan/work', 'mode': 'rw'}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Increase spawn timeout
|
||||||
|
c.Spawner.start_timeout = 120
|
||||||
|
c.Spawner.http_timeout = 120
|
||||||
|
|
||||||
|
# Set notebook directory and default interface
|
||||||
|
c.Spawner.notebook_dir = '/home/jovyan/work'
|
||||||
|
c.Spawner.default_url = '/lab'
|
||||||
|
|
||||||
|
# UI and data locations
|
||||||
|
c.JupyterHub.logo_file = '/srv/jupyterhub/logo.png'
|
||||||
|
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'
|
||||||
|
|
||||||
|
# Note: DockerSpawner creates isolated containers for each user.
|
||||||
|
# Each user gets their own containerized Jupyter environment with persistent storage.
|
||||||
2
main.py
2
main.py
|
|
@ -18,6 +18,7 @@ SERVER_HOSTNAME = os.environ.get("SERVER_HOSTNAME", "einszwovier.local")
|
||||||
BOOKSTACK_PORT = os.environ.get("BOOKSTACK_PORT", "6875")
|
BOOKSTACK_PORT = os.environ.get("BOOKSTACK_PORT", "6875")
|
||||||
OPENWEBUI_PORT = os.environ.get("OPENWEBUI_PORT", "8080")
|
OPENWEBUI_PORT = os.environ.get("OPENWEBUI_PORT", "8080")
|
||||||
PORTAINER_PORT = os.environ.get("PORTAINER_PORT", "9000")
|
PORTAINER_PORT = os.environ.get("PORTAINER_PORT", "9000")
|
||||||
|
FORGEJO_PORT = os.environ.get("FORGEJO_PORT", "3003")
|
||||||
|
|
||||||
# Courses CSV path
|
# Courses CSV path
|
||||||
COURSES_CSV = Path("data/courses.csv")
|
COURSES_CSV = Path("data/courses.csv")
|
||||||
|
|
@ -84,6 +85,7 @@ async def welcome(request: Request):
|
||||||
"bookstack_port": BOOKSTACK_PORT,
|
"bookstack_port": BOOKSTACK_PORT,
|
||||||
"openwebui_port": OPENWEBUI_PORT,
|
"openwebui_port": OPENWEBUI_PORT,
|
||||||
"portainer_port": PORTAINER_PORT,
|
"portainer_port": PORTAINER_PORT,
|
||||||
|
"forgejo_port": FORGEJO_PORT,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,9 @@ sleep 10 # Wait for MariaDB to start
|
||||||
|
|
||||||
zcat "$BACKUP_DIR/bookstack_db_$DATE.sql.gz" | \
|
zcat "$BACKUP_DIR/bookstack_db_$DATE.sql.gz" | \
|
||||||
docker exec -i bookstack-mariadb mariadb \
|
docker exec -i bookstack-mariadb mariadb \
|
||||||
-u"${DB_USERNAME:-bookstack}" \
|
-u"${BOOKSTACK_DB_USERNAME:-bookstack}" \
|
||||||
-p"${DB_PASSWORD}" \
|
-p"${BOOKSTACK_DB_PASSWORD}" \
|
||||||
"${DB_DATABASE:-bookstack}"
|
"${BOOKSTACK_DB_DATABASE:-bookstack}"
|
||||||
|
|
||||||
# 2. Restore BookStack files
|
# 2. Restore BookStack files
|
||||||
echo "Restoring BookStack files..."
|
echo "Restoring BookStack files..."
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,18 @@
|
||||||
<div class="tagline">Teste unsere schulischen Large Language Models direkt über die Weboberfläche.</div>
|
<div class="tagline">Teste unsere schulischen Large Language Models direkt über die Weboberfläche.</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a class="link-card" href="http://{{ server_hostname }}:8001" target="_blank">
|
||||||
|
<div class="title">JupyterHub</div>
|
||||||
|
<div class="tagline">Interaktive Python-Notebooks für Datenanalyse, Visualisierung und kollaboratives
|
||||||
|
Programmieren. Schreib uns eine E-Mail für Login-Daten.</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="link-card" href="http://{{ server_hostname }}:{{ forgejo_port }}" target="_blank">
|
||||||
|
<div class="title">Forgejo Git Server</div>
|
||||||
|
<div class="tagline">Versionskontrolle und Code-Hosting für kollaborative Softwareprojekte. Ideal für gemeinsame
|
||||||
|
Entwicklung und Projektmanagement.</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a class="link-card" href="mailto:einszwovier@gvb-gymnasium.de" target="_blank">
|
<a class="link-card" href="mailto:einszwovier@gvb-gymnasium.de" target="_blank">
|
||||||
<div class="title">Kontakt</div>
|
<div class="title">Kontakt</div>
|
||||||
<div class="tagline">Schreibe uns direkt an: einszwovier@gvb-gymnasium.de</div>
|
<div class="tagline">Schreibe uns direkt an: einszwovier@gvb-gymnasium.de</div>
|
||||||
|
|
@ -70,7 +82,8 @@
|
||||||
<!-- Footer with Admin Panel and Source Link -->
|
<!-- Footer with Admin Panel and Source Link -->
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="footer-container">
|
<div class="footer-container">
|
||||||
<a href="http://{{ server_hostname }}:{{ portainer_port }}" target="_blank" class="admin-link">Admin Panel (Portainer)</a>
|
<a href="http://{{ server_hostname }}:{{ portainer_port }}" target="_blank" class="admin-link">Admin Panel
|
||||||
|
(Portainer)</a>
|
||||||
<span class="footer-source">
|
<span class="footer-source">
|
||||||
| <a href="https://forgejo.petau.net/aron/124-webapp" target="_blank">Quellcode der Website</a>
|
| <a href="https://forgejo.petau.net/aron/124-webapp" target="_blank">Quellcode der Website</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue