leafdocs 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- leafdocs-0.1.0/LICENSE +21 -0
- leafdocs-0.1.0/PKG-INFO +310 -0
- leafdocs-0.1.0/README.md +286 -0
- leafdocs-0.1.0/pyproject.toml +35 -0
- leafdocs-0.1.0/setup.cfg +4 -0
- leafdocs-0.1.0/src/leafdocs/__init__.py +3 -0
- leafdocs-0.1.0/src/leafdocs/app.py +178 -0
- leafdocs-0.1.0/src/leafdocs/static/favicon.ico +0 -0
- leafdocs-0.1.0/src/leafdocs/templates/base.html +94 -0
- leafdocs-0.1.0/src/leafdocs/templates/index.html +141 -0
- leafdocs-0.1.0/src/leafdocs/templates/login.html +108 -0
- leafdocs-0.1.0/src/leafdocs/templates/reader.html +145 -0
- leafdocs-0.1.0/src/leafdocs.egg-info/PKG-INFO +310 -0
- leafdocs-0.1.0/src/leafdocs.egg-info/SOURCES.txt +16 -0
- leafdocs-0.1.0/src/leafdocs.egg-info/dependency_links.txt +1 -0
- leafdocs-0.1.0/src/leafdocs.egg-info/requires.txt +9 -0
- leafdocs-0.1.0/src/leafdocs.egg-info/top_level.txt +1 -0
- leafdocs-0.1.0/tests/test_app.py +211 -0
leafdocs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anshul Raj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
leafdocs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: leafdocs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turn a directory of Markdown files into a self-hosted, searchable web reader.
|
|
5
|
+
Author-email: Anshul <you@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: markdown,docs,flask,self-hosted
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: Flask
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: flask>=3.0
|
|
16
|
+
Requires-Dist: markdown>=3.5
|
|
17
|
+
Requires-Dist: python-frontmatter>=1.1
|
|
18
|
+
Requires-Dist: bcrypt>=4.1
|
|
19
|
+
Requires-Dist: python-dotenv>=1.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-flask; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# leafdocs
|
|
26
|
+
|
|
27
|
+
A lightweight Python library that turns a directory of Markdown files into a self-hosted, searchable web reader.
|
|
28
|
+
|
|
29
|
+
Install it, point it at a folder, get a running Flask app you can deploy anywhere.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from leafdocs import LeafDocs
|
|
33
|
+
|
|
34
|
+
ld = LeafDocs(docs_dir="./docs")
|
|
35
|
+
ld.run()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install leafdocs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Minimal setup
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from leafdocs import LeafDocs
|
|
54
|
+
|
|
55
|
+
ld = LeafDocs(docs_dir="./docs")
|
|
56
|
+
ld.run()
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Visit `http://127.0.0.1:5000` — you'll see a searchable index of all `.md` files in your `docs/` directory.
|
|
60
|
+
|
|
61
|
+
### Constructor arguments
|
|
62
|
+
|
|
63
|
+
| Argument | Type | Default | Description |
|
|
64
|
+
|--------------|-----------------|-----------------|--------------------------------------------------|
|
|
65
|
+
| `docs_dir` | `str` | `"./docs"` | Path to the directory containing `.md` files |
|
|
66
|
+
| `secret_key` | `str` or `None` | `None` | Flask session secret key (see Auth section) |
|
|
67
|
+
|
|
68
|
+
### Accessing the Flask app
|
|
69
|
+
|
|
70
|
+
The underlying Flask app is exposed as `ld.flask_app`. Use it to add routes, middleware, or blueprints:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from leafdocs import LeafDocs
|
|
74
|
+
|
|
75
|
+
ld = LeafDocs(docs_dir="./docs")
|
|
76
|
+
|
|
77
|
+
@ld.flask_app.route("/health")
|
|
78
|
+
def health():
|
|
79
|
+
return {"status": "ok"}
|
|
80
|
+
|
|
81
|
+
ld.run()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Frontmatter
|
|
87
|
+
|
|
88
|
+
Each `.md` file can optionally include YAML frontmatter. Supported fields:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
---
|
|
92
|
+
title: My Document Title
|
|
93
|
+
tags: [python, tutorial]
|
|
94
|
+
---
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| Field | Type | Fallback |
|
|
98
|
+
|---------|--------------------------------|----------------------|
|
|
99
|
+
| `title` | string | Filename, titlecased |
|
|
100
|
+
| `tags` | list or comma-separated string | None |
|
|
101
|
+
|
|
102
|
+
Frontmatter is optional — files without it are discovered and served normally.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Authentication
|
|
107
|
+
|
|
108
|
+
By default the server runs open with no login required.
|
|
109
|
+
|
|
110
|
+
To enable pin-based auth, add pins to a `.env` file in your working directory:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
LEAFDOCS_PINS=mypin123,anotherpin
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Restart the server. All routes will now require a valid pin.
|
|
117
|
+
|
|
118
|
+
**How it works:**
|
|
119
|
+
- Pins are hashed with bcrypt at startup — raw values are never stored in memory
|
|
120
|
+
- A successful login issues an `httponly` session cookie
|
|
121
|
+
- Multiple pins are supported — useful for sharing access without a shared secret
|
|
122
|
+
- To invalidate all sessions, rotate or remove the pin and restart
|
|
123
|
+
|
|
124
|
+
### Session secret key
|
|
125
|
+
|
|
126
|
+
By default a random secret key is generated at startup, which means sessions are invalidated on every restart. For persistent sessions across restarts, set a stable key:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
LEAFDOCS_SECRET_KEY=your-long-random-secret-here
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Or pass it directly in code:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
ld = LeafDocs(docs_dir="./docs", secret_key="your-long-random-secret-here")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Generate a good key with:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
python -c "import secrets; print(secrets.token_hex(32))"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Configuration reference
|
|
147
|
+
|
|
148
|
+
All pins and the secret key are configured via `.env` only. Copy `.env.example` to `.env` to get started:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
LEAFDOCS_PINS=pin1,pin2
|
|
152
|
+
LEAFDOCS_SECRET_KEY=your-secret-here
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Known limitations
|
|
158
|
+
|
|
159
|
+
- **No caching** — Markdown is rendered on every request. Fine for personal or low-traffic use; not suitable for high-traffic production serving.
|
|
160
|
+
- **No per-device session revocation** — rotating the pin invalidates all active sessions across all devices.
|
|
161
|
+
- **No plugin system** — the primary extension hook is `ld.flask_app`. Add routes and middleware directly on the Flask app.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Deployment
|
|
166
|
+
|
|
167
|
+
> **Disclaimer:** leafdocs is a Flask application. Running it with `ld.run()` uses Flask's built-in development server, which is not suitable for production. For production use, you are responsible for:
|
|
168
|
+
> - Running behind a production WSGI server (Gunicorn recommended)
|
|
169
|
+
> - Terminating HTTPS at a reverse proxy (Nginx recommended)
|
|
170
|
+
> - Managing the process with a supervisor (systemd recommended)
|
|
171
|
+
>
|
|
172
|
+
> The instructions below cover a standard Nginx + Gunicorn + systemd setup.
|
|
173
|
+
|
|
174
|
+
### AWS EC2 / GCP Compute Engine
|
|
175
|
+
|
|
176
|
+
The steps are identical for both — the only difference is how you provision the VM.
|
|
177
|
+
|
|
178
|
+
#### 1. Provision a VM
|
|
179
|
+
|
|
180
|
+
- **AWS:** Launch an EC2 instance (Ubuntu 24.04 LTS, `t3.micro` or larger). Open ports 80 and 443 in the security group.
|
|
181
|
+
- **GCP:** Create a Compute Engine instance (Ubuntu 24.04 LTS, `e2-micro` or larger). Open ports 80 and 443 in the firewall rules.
|
|
182
|
+
|
|
183
|
+
SSH into the instance.
|
|
184
|
+
|
|
185
|
+
#### 2. Install dependencies
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
sudo apt update && sudo apt install -y python3-pip python3-venv nginx
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### 3. Set up the app
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
mkdir ~/leafdocs-app && cd ~/leafdocs-app
|
|
195
|
+
python3 -m venv .venv
|
|
196
|
+
source .venv/bin/activate
|
|
197
|
+
pip install leafdocs gunicorn
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Create your `docs/` directory and add your `.md` files:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
mkdir docs
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Create `main.py`:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from leafdocs import LeafDocs
|
|
210
|
+
|
|
211
|
+
ld = LeafDocs(docs_dir="./docs")
|
|
212
|
+
app = ld.flask_app
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Create `.env`:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
LEAFDOCS_PINS=yourpin
|
|
219
|
+
LEAFDOCS_SECRET_KEY=your-long-random-secret-here
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Test it runs:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
gunicorn --bind 127.0.0.1:8000 main:app
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### 4. Configure systemd
|
|
229
|
+
|
|
230
|
+
Create `/etc/systemd/system/leafdocs.service`:
|
|
231
|
+
|
|
232
|
+
```ini
|
|
233
|
+
[Unit]
|
|
234
|
+
Description=LeafDocs
|
|
235
|
+
After=network.target
|
|
236
|
+
|
|
237
|
+
[Service]
|
|
238
|
+
User=ubuntu
|
|
239
|
+
WorkingDirectory=/home/ubuntu/leafdocs-app
|
|
240
|
+
Environment="PATH=/home/ubuntu/leafdocs-app/.venv/bin"
|
|
241
|
+
ExecStart=/home/ubuntu/leafdocs-app/.venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 main:app
|
|
242
|
+
Restart=always
|
|
243
|
+
|
|
244
|
+
[Install]
|
|
245
|
+
WantedBy=multi-user.target
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Enable and start:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
sudo systemctl daemon-reload
|
|
252
|
+
sudo systemctl enable leafdocs
|
|
253
|
+
sudo systemctl start leafdocs
|
|
254
|
+
sudo systemctl status leafdocs
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### 5. Configure Nginx
|
|
258
|
+
|
|
259
|
+
Create `/etc/nginx/sites-available/leafdocs`:
|
|
260
|
+
|
|
261
|
+
```nginx
|
|
262
|
+
server {
|
|
263
|
+
listen 80;
|
|
264
|
+
server_name your-domain.com;
|
|
265
|
+
|
|
266
|
+
location / {
|
|
267
|
+
proxy_pass http://127.0.0.1:8000;
|
|
268
|
+
proxy_set_header Host $host;
|
|
269
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
270
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
271
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Enable and reload:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
sudo ln -s /etc/nginx/sites-available/leafdocs /etc/nginx/sites-enabled/
|
|
280
|
+
sudo nginx -t
|
|
281
|
+
sudo systemctl reload nginx
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### 6. Enable HTTPS
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
sudo apt install -y certbot python3-certbot-nginx
|
|
288
|
+
sudo certbot --nginx -d your-domain.com
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Certbot will automatically update your Nginx config and set up auto-renewal.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Development
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
git clone https://github.com/anshulraj10/leafdocs
|
|
299
|
+
cd leafdocs
|
|
300
|
+
python -m venv .venv
|
|
301
|
+
source .venv/bin/activate
|
|
302
|
+
pip install -e ".[dev]"
|
|
303
|
+
pytest tests/ -v
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT
|
leafdocs-0.1.0/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# leafdocs
|
|
2
|
+
|
|
3
|
+
A lightweight Python library that turns a directory of Markdown files into a self-hosted, searchable web reader.
|
|
4
|
+
|
|
5
|
+
Install it, point it at a folder, get a running Flask app you can deploy anywhere.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from leafdocs import LeafDocs
|
|
9
|
+
|
|
10
|
+
ld = LeafDocs(docs_dir="./docs")
|
|
11
|
+
ld.run()
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install leafdocs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Minimal setup
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from leafdocs import LeafDocs
|
|
30
|
+
|
|
31
|
+
ld = LeafDocs(docs_dir="./docs")
|
|
32
|
+
ld.run()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Visit `http://127.0.0.1:5000` — you'll see a searchable index of all `.md` files in your `docs/` directory.
|
|
36
|
+
|
|
37
|
+
### Constructor arguments
|
|
38
|
+
|
|
39
|
+
| Argument | Type | Default | Description |
|
|
40
|
+
|--------------|-----------------|-----------------|--------------------------------------------------|
|
|
41
|
+
| `docs_dir` | `str` | `"./docs"` | Path to the directory containing `.md` files |
|
|
42
|
+
| `secret_key` | `str` or `None` | `None` | Flask session secret key (see Auth section) |
|
|
43
|
+
|
|
44
|
+
### Accessing the Flask app
|
|
45
|
+
|
|
46
|
+
The underlying Flask app is exposed as `ld.flask_app`. Use it to add routes, middleware, or blueprints:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from leafdocs import LeafDocs
|
|
50
|
+
|
|
51
|
+
ld = LeafDocs(docs_dir="./docs")
|
|
52
|
+
|
|
53
|
+
@ld.flask_app.route("/health")
|
|
54
|
+
def health():
|
|
55
|
+
return {"status": "ok"}
|
|
56
|
+
|
|
57
|
+
ld.run()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Frontmatter
|
|
63
|
+
|
|
64
|
+
Each `.md` file can optionally include YAML frontmatter. Supported fields:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
---
|
|
68
|
+
title: My Document Title
|
|
69
|
+
tags: [python, tutorial]
|
|
70
|
+
---
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Field | Type | Fallback |
|
|
74
|
+
|---------|--------------------------------|----------------------|
|
|
75
|
+
| `title` | string | Filename, titlecased |
|
|
76
|
+
| `tags` | list or comma-separated string | None |
|
|
77
|
+
|
|
78
|
+
Frontmatter is optional — files without it are discovered and served normally.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Authentication
|
|
83
|
+
|
|
84
|
+
By default the server runs open with no login required.
|
|
85
|
+
|
|
86
|
+
To enable pin-based auth, add pins to a `.env` file in your working directory:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
LEAFDOCS_PINS=mypin123,anotherpin
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Restart the server. All routes will now require a valid pin.
|
|
93
|
+
|
|
94
|
+
**How it works:**
|
|
95
|
+
- Pins are hashed with bcrypt at startup — raw values are never stored in memory
|
|
96
|
+
- A successful login issues an `httponly` session cookie
|
|
97
|
+
- Multiple pins are supported — useful for sharing access without a shared secret
|
|
98
|
+
- To invalidate all sessions, rotate or remove the pin and restart
|
|
99
|
+
|
|
100
|
+
### Session secret key
|
|
101
|
+
|
|
102
|
+
By default a random secret key is generated at startup, which means sessions are invalidated on every restart. For persistent sessions across restarts, set a stable key:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
LEAFDOCS_SECRET_KEY=your-long-random-secret-here
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Or pass it directly in code:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
ld = LeafDocs(docs_dir="./docs", secret_key="your-long-random-secret-here")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Generate a good key with:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
python -c "import secrets; print(secrets.token_hex(32))"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Configuration reference
|
|
123
|
+
|
|
124
|
+
All pins and the secret key are configured via `.env` only. Copy `.env.example` to `.env` to get started:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
LEAFDOCS_PINS=pin1,pin2
|
|
128
|
+
LEAFDOCS_SECRET_KEY=your-secret-here
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Known limitations
|
|
134
|
+
|
|
135
|
+
- **No caching** — Markdown is rendered on every request. Fine for personal or low-traffic use; not suitable for high-traffic production serving.
|
|
136
|
+
- **No per-device session revocation** — rotating the pin invalidates all active sessions across all devices.
|
|
137
|
+
- **No plugin system** — the primary extension hook is `ld.flask_app`. Add routes and middleware directly on the Flask app.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Deployment
|
|
142
|
+
|
|
143
|
+
> **Disclaimer:** leafdocs is a Flask application. Running it with `ld.run()` uses Flask's built-in development server, which is not suitable for production. For production use, you are responsible for:
|
|
144
|
+
> - Running behind a production WSGI server (Gunicorn recommended)
|
|
145
|
+
> - Terminating HTTPS at a reverse proxy (Nginx recommended)
|
|
146
|
+
> - Managing the process with a supervisor (systemd recommended)
|
|
147
|
+
>
|
|
148
|
+
> The instructions below cover a standard Nginx + Gunicorn + systemd setup.
|
|
149
|
+
|
|
150
|
+
### AWS EC2 / GCP Compute Engine
|
|
151
|
+
|
|
152
|
+
The steps are identical for both — the only difference is how you provision the VM.
|
|
153
|
+
|
|
154
|
+
#### 1. Provision a VM
|
|
155
|
+
|
|
156
|
+
- **AWS:** Launch an EC2 instance (Ubuntu 24.04 LTS, `t3.micro` or larger). Open ports 80 and 443 in the security group.
|
|
157
|
+
- **GCP:** Create a Compute Engine instance (Ubuntu 24.04 LTS, `e2-micro` or larger). Open ports 80 and 443 in the firewall rules.
|
|
158
|
+
|
|
159
|
+
SSH into the instance.
|
|
160
|
+
|
|
161
|
+
#### 2. Install dependencies
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
sudo apt update && sudo apt install -y python3-pip python3-venv nginx
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### 3. Set up the app
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
mkdir ~/leafdocs-app && cd ~/leafdocs-app
|
|
171
|
+
python3 -m venv .venv
|
|
172
|
+
source .venv/bin/activate
|
|
173
|
+
pip install leafdocs gunicorn
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Create your `docs/` directory and add your `.md` files:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
mkdir docs
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Create `main.py`:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from leafdocs import LeafDocs
|
|
186
|
+
|
|
187
|
+
ld = LeafDocs(docs_dir="./docs")
|
|
188
|
+
app = ld.flask_app
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Create `.env`:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
LEAFDOCS_PINS=yourpin
|
|
195
|
+
LEAFDOCS_SECRET_KEY=your-long-random-secret-here
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Test it runs:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
gunicorn --bind 127.0.0.1:8000 main:app
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### 4. Configure systemd
|
|
205
|
+
|
|
206
|
+
Create `/etc/systemd/system/leafdocs.service`:
|
|
207
|
+
|
|
208
|
+
```ini
|
|
209
|
+
[Unit]
|
|
210
|
+
Description=LeafDocs
|
|
211
|
+
After=network.target
|
|
212
|
+
|
|
213
|
+
[Service]
|
|
214
|
+
User=ubuntu
|
|
215
|
+
WorkingDirectory=/home/ubuntu/leafdocs-app
|
|
216
|
+
Environment="PATH=/home/ubuntu/leafdocs-app/.venv/bin"
|
|
217
|
+
ExecStart=/home/ubuntu/leafdocs-app/.venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 main:app
|
|
218
|
+
Restart=always
|
|
219
|
+
|
|
220
|
+
[Install]
|
|
221
|
+
WantedBy=multi-user.target
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Enable and start:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
sudo systemctl daemon-reload
|
|
228
|
+
sudo systemctl enable leafdocs
|
|
229
|
+
sudo systemctl start leafdocs
|
|
230
|
+
sudo systemctl status leafdocs
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### 5. Configure Nginx
|
|
234
|
+
|
|
235
|
+
Create `/etc/nginx/sites-available/leafdocs`:
|
|
236
|
+
|
|
237
|
+
```nginx
|
|
238
|
+
server {
|
|
239
|
+
listen 80;
|
|
240
|
+
server_name your-domain.com;
|
|
241
|
+
|
|
242
|
+
location / {
|
|
243
|
+
proxy_pass http://127.0.0.1:8000;
|
|
244
|
+
proxy_set_header Host $host;
|
|
245
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
246
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
247
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Enable and reload:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
sudo ln -s /etc/nginx/sites-available/leafdocs /etc/nginx/sites-enabled/
|
|
256
|
+
sudo nginx -t
|
|
257
|
+
sudo systemctl reload nginx
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### 6. Enable HTTPS
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
sudo apt install -y certbot python3-certbot-nginx
|
|
264
|
+
sudo certbot --nginx -d your-domain.com
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Certbot will automatically update your Nginx config and set up auto-renewal.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Development
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
git clone https://github.com/anshulraj10/leafdocs
|
|
275
|
+
cd leafdocs
|
|
276
|
+
python -m venv .venv
|
|
277
|
+
source .venv/bin/activate
|
|
278
|
+
pip install -e ".[dev]"
|
|
279
|
+
pytest tests/ -v
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "leafdocs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Turn a directory of Markdown files into a self-hosted, searchable web reader."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Anshul", email = "you@example.com" }]
|
|
13
|
+
keywords = ["markdown", "docs", "flask", "self-hosted"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Framework :: Flask",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"flask>=3.0",
|
|
22
|
+
"markdown>=3.5",
|
|
23
|
+
"python-frontmatter>=1.1",
|
|
24
|
+
"bcrypt>=4.1",
|
|
25
|
+
"python-dotenv>=1.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = ["pytest", "pytest-flask"]
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
"leafdocs" = ["templates/**", "static/**"]
|
leafdocs-0.1.0/setup.cfg
ADDED