Skip to content
Merged

Dev #626

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ ansible/test.json
FastapiOpenRestyConfigurator/.env
template_path
backend_path
*/plans
*/plans
.vscode/settings.json
65 changes: 65 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

### Development
- **Install Dependencies**:
```bash
pip install -r FastapiOpenRestyConfigurator/requirements.txt
```
- **Run Application (Development)**:
```bash
# From project root
export PYTHONPATH=$PYTHONPATH:$(pwd)/FastapiOpenRestyConfigurator
uvicorn FastapiOpenRestyConfigurator.main:app --reload
```
- **Run Application (Production)**:
```bash
# Using gunicorn with provided config
gunicorn -c FastapiOpenRestyConfigurator/gunicorn_conf.py FastapiOpenRestyConfigurator.main:app
```

### Testing
- **Run All Tests**:
```bash
# From project root
export PYTHONPATH=$PYTHONPATH:$(pwd)/FastapiOpenRestyConfigurator
pytest FastapiOpenRestyConfigurator/tests
```
- **Run Single Test File**:
```bash
export PYTHONPATH=$PYTHONPATH:$(pwd)/FastapiOpenRestyConfigurator
pytest FastapiOpenRestyConfigurator/tests/test_specific_file.py
```

## Architecture

The project (Flask OpenResty Configurator - FORC) is a FastAPI-based service that dynamically generates NGINX configuration snippets for an OpenResty web server.

### High-Level Flow
1. **Request**: A REST API request is received by a `view`.
2. **Logic**: The `view` calls a `service` to perform business logic (e.g., creating a new backend).
3. **Templating**: The `service` uses Jinja2 templates to generate a configuration snippet.
4. **Persistence**: The snippet is written to the filesystem (`FORC_BACKEND_PATH`).
5. **Activation**: OpenResty is reloaded to apply the new configuration.

### Project Structure (`FastapiOpenRestyConfigurator/`)
- `main.py`: Entry point; initializes the FastAPI app and includes routers.
- `app/main/views/`: API endpoints (Controllers).
- `app/main/service/`: Core business logic.
- `backend.py`: Manages backend configurations.
- `openresty.py`: Handles OpenResty interactions (e.g., reloading).
- `template.py`: Manages Jinja2 template rendering.
- `user.py`: User management logic.
- `app/main/model/`: Data models and Pydantic serializers.
- `app/main/util/`: Shared utilities for authentication, logging, and templating.
- `tests/`: Integration and unit tests.

### Configuration
The application is configured via environment variables:
- `FORC_SECRET_KEY`: Encryption key for the service.
- `FORC_API_KEY`: API key for `X-API-KEY` authentication.
- `FORC_BACKEND_PATH`: Filesystem path where NGINX config snippets are stored.
- `FORC_TEMPLATE_PATH`: Filesystem path where Jinja2 templates are located.
1 change: 1 addition & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ COPY docker/html /usr/local/openresty/nginx/html
RUN mkdir -p ${FORC_BACKEND_PATH} ${FORC_TEMPLATE_PATH} /opt/scripts \
&& chmod +x launch.sh /opt/scripts/generate_ip_blocklists.sh
COPY examples/templates ${FORC_TEMPLATE_PATH}
COPY examples/scripts /var/forc/scripts/

EXPOSE 5000
CMD ["./launch.sh"]
3 changes: 2 additions & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
forc:
image: forc
image: oci.bi.denbi.de/simplevm/forc:dev
container_name: forc
restart: always
env_file:
Expand All @@ -10,6 +10,7 @@ services:
#- /var/forc/template_path/:/var/forc/template_path/:rw # optional default has the exmaples/templates
- /etc/letsencrypt/:/etc/letsencrypt/:r #needs to provided for cert
- .env.forc:/opt/simpleVMWebGateway/FastapiOpenRestyConfigurator/.env # needs to be mounted for roc

ports:
- 0.0.0.0:5000:5000
- 0.0.0.0:80:80
Expand Down
18 changes: 16 additions & 2 deletions docker/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ events {
http {
include mime.types;

lua_package_path "{{ FORC_BACKEND_PATH }}/scripts/?.lua;;";
lua_package_path "/var/forc/scripts/?.lua;;";

default_type application/octet-stream;

Expand Down Expand Up @@ -355,6 +355,20 @@ init_by_lua_block {



location = /consent {
content_by_lua_block {
local consent_service = require("consent_service")
consent_service.render_consent_page()
}
}

location = /consent/callback {
content_by_lua_block {
local consent_service = require("consent_service")
consent_service.handle_consent_post()
}
}

include {{ FORC_BACKEND_PATH }}/*.conf;


Expand All @@ -379,4 +393,4 @@ init_by_lua_block {
root html;
}
}
}
}
171 changes: 171 additions & 0 deletions examples/scripts/consent_page.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
-- examples/scripts/consent_html.lua
local _M = {}

function _M.render()
return[[
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Consent Required</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Helvetica,
Arial,
sans-serif;
background: #ffffff;
color: #222633;
}
.card {
width: 100%;
max-width: 800px;
background: #ffffff;
border: 1px solid #DCDDE5;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 6px 20px rgba(34, 38, 48, 0.12);
}
.card-header {
display: flex;
align-items: center;
gap: 1rem;
background: #222630;
padding: 1.25rem 2rem;
}
.logo {
width: 54px;
height: 54px;
object-fit: contain;
}
.brand {
color: #D0E9EA;
font-size: 1.65rem;
font-weight: 400;
letter-spacing: -0.02em;
}
.brand strong {
font-weight: 600;
}
.card-body {
padding: 2rem;
}
.notice {
padding: 1.25rem;
margin-bottom: 1.5rem;
background: #D0E9EA;
color: #254B4C;
border-radius: 4px;
line-height: 1.55;
}
.notice strong {
color: #222633;
}
p {
color: #343A48;
line-height: 1.6;
margin: 0 0 1.25rem;
}
a {
color: #254B4C;
font-weight: 600;
text-decoration: underline;
text-underline-offset: 2px;
}
a:hover {
text-decoration-thickness: 2px;
}
form {
margin-top: 2rem;
}
.btn {
width: 100%;
padding: 0.85rem 1.5rem;

border: none;
border-radius: 4px;

background: #2A888D;
color: #ffffff;

font-size: 1rem;
font-weight: 600;

cursor: pointer;
transition: background 0.15s ease;
}
.btn:hover {
background: #2D313D;
}
.btn:focus-visible,
a:focus-visible {
outline: 3px solid #D0E9EA;
outline-offset: 3px;
}
@media (max-width: 600px) {
body {
padding: 1rem;
}
.card-header,
.card-body {
padding: 1.5rem;
}
}
</style>
</head>
<body>
<main class="card">
<div class="card-header">
<img
src="https://simplevm.denbi.de/portal/webapp/assets/simplevm_favicon.png"
alt="SimpleVM Logo"
class="logo"
>
<span class="brand">
<strong>SimpleVM</strong> Web Services
</span>
</div>
<div class="card-body">
<p>
The service you are about to access is provided by its users.
Neither SimpleVM nor de.NBI Cloud is responsible for the content
provided through this service.
<br/><br/>
By continuing, you agree to the
<a
href="https://cloud.denbi.de/about/policies/"
target="_blank"
rel="noopener noreferrer"
>
Terms of Service and Privacy Policy
</a>.
</p>

<form method="POST" action="/consent/callback">
<button type="submit" class="btn">
Agree and Continue
</button>
</form>

</div>
</main>
</body>
</html>
]]
end

return _M
Loading
Loading