feat: Initial project setup with Wiki.js and management scripts
- Add Docker Compose setup for Wiki.js and PostgreSQL. - Include setup and backup scripts for easy management. - Create command-line scripts to create and edit wiki pages via the GraphQL API. - Add comprehensive README and .gitignore.
This commit is contained in:
13
.env
13
.env
@@ -1,13 +0,0 @@
|
|||||||
# Database Configuration
|
|
||||||
# Generate a secure password for your database
|
|
||||||
# You can use: openssl rand -base64 32
|
|
||||||
DB_PASSWORD=your_secure_database_password_here
|
|
||||||
|
|
||||||
# Wiki.js Configuration
|
|
||||||
# The domain where your wiki will be accessible
|
|
||||||
# For local development, use localhost:3000
|
|
||||||
# For production, use your actual domain
|
|
||||||
WIKI_URL=http://localhost:3000
|
|
||||||
|
|
||||||
# Optional: Set timezone (default is UTC)
|
|
||||||
TZ=Europe/London
|
|
||||||
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python
|
||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Backups
|
||||||
|
backups/
|
||||||
|
|
||||||
|
# Schema file
|
||||||
|
introspectionSchema.json
|
||||||
|
|
||||||
|
# IDE / OS specific
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
192
README.md
192
README.md
@@ -1,155 +1,103 @@
|
|||||||
# Personal Wiki.js Setup for Home Server
|
# Personal Wiki.js Project
|
||||||
|
|
||||||
This repository contains everything you need to set up your personal Wiki.js instance on your home server.
|
This repository contains everything needed to set up, manage, and programmatically interact with a personal Wiki.js instance using Docker and Python scripts.
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
1. **Run the setup script:**
|
1. **Set Up Environment**: Ensure you have `docker` and `docker-compose` installed. The `setup.sh` script will check for these.
|
||||||
|
|
||||||
|
2. **Run Setup Script**: This script generates a secure database password, creates necessary directories, and starts the wiki.
|
||||||
```bash
|
```bash
|
||||||
./setup.sh
|
./setup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Access your wiki:**
|
3. **Access Your Wiki**: Open your browser and navigate to the URL specified in your `.env` file (e.g., `http://localhost:3000` or `https://wiki.liveaodh.com`). Complete the initial setup wizard and create your administrator account.
|
||||||
- Open http://localhost:3000 in your browser
|
|
||||||
- Complete the initial setup wizard
|
|
||||||
- Create your administrator account
|
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
wikiaodh/
|
wikiaodh/
|
||||||
├── docker-compose.yml # Docker services configuration
|
├── docker-compose.yml # Docker services configuration
|
||||||
├── .env # Environment variables (passwords, etc.)
|
├── .env # Environment variables (passwords, API key, etc.)
|
||||||
├── setup.sh # Automated setup script
|
├── setup.sh # Automated setup script
|
||||||
|
├── backup.sh # Backup script for database and assets
|
||||||
├── README.md # This file
|
├── README.md # This file
|
||||||
└── data/ # Created automatically for persistent data
|
├── requirements.txt # Python dependencies for scripts
|
||||||
├── wiki/ # Wiki application data
|
├── venv/ # Python virtual environment
|
||||||
├── db/ # PostgreSQL database files
|
└── scripts/
|
||||||
└── assets/ # Uploaded media and assets
|
├── create_page.py # CLI tool to create new wiki pages
|
||||||
|
└── edit_page.py # CLI tool to edit existing wiki pages
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔧 Configuration
|
## 🔧 Wiki Management
|
||||||
|
|
||||||
### Environment Variables (.env)
|
### Docker Commands
|
||||||
- `DB_PASSWORD`: Secure password for PostgreSQL database
|
|
||||||
- `WIKI_URL`: The URL where your wiki will be accessible
|
|
||||||
- `TZ`: Your timezone (default: Europe/London)
|
|
||||||
|
|
||||||
### Ports
|
- **Start the wiki**: `docker-compose up -d`
|
||||||
- **3000**: Wiki.js web interface (http://localhost:3000)
|
- **Stop the wiki**: `docker-compose down`
|
||||||
- **5432**: PostgreSQL database (internal only)
|
- **View logs**: `docker-compose logs -f`
|
||||||
|
- **Restart services**: `docker-compose restart`
|
||||||
|
- **Update to latest version**: `docker-compose pull && docker-compose up -d`
|
||||||
|
|
||||||
## 📋 Management Commands
|
### Backup and Restore
|
||||||
|
|
||||||
|
- **Run a full backup**: The `backup.sh` script creates a timestamped backup of your database and assets in the `backups/` directory.
|
||||||
|
```bash
|
||||||
|
./backup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤖 Command-Line Scripts
|
||||||
|
|
||||||
|
These scripts use the Wiki.js GraphQL API to manage content programmatically. They require a Python virtual environment and an API key.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. **Activate Virtual Environment**:
|
||||||
|
```bash
|
||||||
|
source venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Set API Key**: Generate an API key in the Wiki.js **Administration -> API Access** area and add it to your `.env` file:
|
||||||
|
```
|
||||||
|
WIKI_API_KEY=your_api_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### `scripts/create_page.py`
|
||||||
|
|
||||||
|
Creates a new page in your wiki.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
```bash
|
```bash
|
||||||
# Start the wiki
|
python3 scripts/create_page.py --path "my-folder/my-new-page" --title "My New Page" --content-file "path/to/my-content.md"
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
# Stop the wiki
|
|
||||||
docker-compose down
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
docker-compose logs -f
|
|
||||||
|
|
||||||
# Restart services
|
|
||||||
docker-compose restart
|
|
||||||
|
|
||||||
# Update to latest version
|
|
||||||
docker-compose pull
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 💾 Backup & Restore
|
**Arguments**:
|
||||||
|
- `--path` (required): The URL path for the new page.
|
||||||
|
- `--title` (required): The title of the page.
|
||||||
|
- `--content-file` (required): Path to a markdown file with the page content.
|
||||||
|
- `--tags` (optional): A list of tags (e.g., `--tags project guide`).
|
||||||
|
|
||||||
### Backup
|
### `scripts/edit_page.py`
|
||||||
|
|
||||||
|
Edits an existing page in your wiki.
|
||||||
|
|
||||||
|
**Usage**:
|
||||||
```bash
|
```bash
|
||||||
# Backup database
|
# Update content
|
||||||
docker-compose exec db pg_dump -U wikijs wiki > backup_$(date +%Y%m%d).sql
|
python3 scripts/edit_page.py --path "home" --content-file "new_content.md"
|
||||||
|
|
||||||
# Backup uploaded files
|
# Update title
|
||||||
tar -czf assets_backup_$(date +%Y%m%d).tar.gz data/assets/
|
python3 scripts/edit_page.py --path "home" --title "New Home Page Title"
|
||||||
|
|
||||||
|
# Update both
|
||||||
|
python3 scripts/edit_page.py --path "home" --title "New Title" --content-file "new_content.md"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Restore
|
**Arguments**:
|
||||||
```bash
|
- `--path` (required): The path of the page to edit.
|
||||||
# Restore database
|
- `--content-file` (optional): Path to a markdown file with the new content.
|
||||||
docker-compose exec -T db psql -U wikijs wiki < backup_YYYYMMDD.sql
|
- `--title` (optional): A new title for the page.
|
||||||
|
|
||||||
# Restore uploaded files
|
|
||||||
tar -xzf assets_backup_YYYYMMDD.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔒 Security Considerations
|
|
||||||
|
|
||||||
1. **Change default passwords**: The setup script generates a secure database password automatically
|
|
||||||
2. **Firewall**: Consider restricting access to port 3000 to your local network only
|
|
||||||
3. **HTTPS**: For production use, set up a reverse proxy with SSL/TLS
|
|
||||||
4. **Regular updates**: Keep Docker images updated with `docker-compose pull`
|
|
||||||
|
|
||||||
## 🌐 Accessing from Other Devices
|
|
||||||
|
|
||||||
To access your wiki from other devices on your network:
|
|
||||||
|
|
||||||
1. Find your server's IP address: `ip addr show`
|
|
||||||
2. Update the `WIKI_URL` in `.env` to use your server's IP
|
|
||||||
3. Access via `http://YOUR_SERVER_IP:3000`
|
|
||||||
|
|
||||||
## 📚 Wiki.js Features
|
|
||||||
|
|
||||||
Your personal wiki includes:
|
|
||||||
|
|
||||||
- **Rich Text Editor**: WYSIWYG and Markdown support
|
|
||||||
- **Media Management**: Upload and organize images, documents, videos
|
|
||||||
- **Search**: Full-text search across all content
|
|
||||||
- **User Management**: Create accounts for family members or team
|
|
||||||
- **Themes**: Light and dark mode support
|
|
||||||
- **Mobile Friendly**: Responsive design for all devices
|
|
||||||
- **Version History**: Track changes to all pages
|
|
||||||
- **Categories & Tags**: Organize content efficiently
|
|
||||||
|
|
||||||
## 🆘 Troubleshooting
|
|
||||||
|
|
||||||
### Wiki won't start
|
|
||||||
```bash
|
|
||||||
# Check logs
|
|
||||||
docker-compose logs
|
|
||||||
|
|
||||||
# Restart services
|
|
||||||
docker-compose down
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database connection issues
|
|
||||||
```bash
|
|
||||||
# Reset database
|
|
||||||
docker-compose down
|
|
||||||
docker volume rm wikiaodh_db-data
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Permission issues
|
|
||||||
```bash
|
|
||||||
# Fix permissions
|
|
||||||
sudo chown -R $USER:$USER data/
|
|
||||||
chmod -R 755 data/
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 Updates
|
|
||||||
|
|
||||||
To update Wiki.js to the latest version:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose down
|
|
||||||
docker-compose pull
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
- [Wiki.js Documentation](https://docs.js.wiki/)
|
|
||||||
- [Wiki.js GitHub](https://github.com/Requarks/wiki)
|
|
||||||
- [Docker Compose Documentation](https://docs.docker.com/compose/)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Enjoy your personal wiki! 📖✨**
|
**Enjoy your powerful, scriptable personal wiki! 📖✨**
|
||||||
|
|||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
python-dotenv
|
||||||
|
requests
|
||||||
101
scripts/create_page.py
Normal file
101
scripts/create_page.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import argparse
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load environment variables from .env file
|
||||||
|
# This will search for a .env file in the current directory or parent directories
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
WIKI_URL = os.getenv("WIKI_URL")
|
||||||
|
API_KEY = os.getenv("WIKI_API_KEY")
|
||||||
|
|
||||||
|
# --- GraphQL Mutation ---
|
||||||
|
GRAPHQL_CREATE_MUTATION = """
|
||||||
|
mutation CreatePage($content: String!, $description: String!, $editor: String!, $isPrivate: Boolean!, $isPublished: Boolean!, $locale: String!, $path: String!, $tags: [String]!, $title: String!) {
|
||||||
|
pages {
|
||||||
|
create(content: $content, description: $description, editor: $editor, isPrivate: $isPrivate, isPublished: $isPublished, locale: $locale, path: $path, tags: $tags, title: $title) {
|
||||||
|
responseResult { succeeded, errorCode, slug, message }
|
||||||
|
page { id, path, title }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_wiki_page(path, title, content_file, tags, locale):
|
||||||
|
"""Sends a GraphQL request to create a new wiki page."""
|
||||||
|
if not all([API_KEY, WIKI_URL]):
|
||||||
|
print("❌ Error: WIKI_API_KEY and/or WIKI_URL not found in environment variables.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(content_file, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"❌ Error: Content file not found at '{content_file}'")
|
||||||
|
return
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"content": content,
|
||||||
|
"description": f"Page for {title}",
|
||||||
|
"editor": "markdown",
|
||||||
|
"isPrivate": False,
|
||||||
|
"isPublished": True,
|
||||||
|
"locale": locale,
|
||||||
|
"path": path,
|
||||||
|
"tags": tags or [],
|
||||||
|
"title": title
|
||||||
|
}
|
||||||
|
|
||||||
|
graphql_query = {
|
||||||
|
"query": GRAPHQL_CREATE_MUTATION,
|
||||||
|
"variables": variables
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"🚀 Sending request to {WIKI_URL}/graphql to create page at path '{path}'...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(f"{WIKI_URL}/graphql", headers=headers, json=graphql_query, timeout=15)
|
||||||
|
response.raise_for_status()
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if 'errors' in result:
|
||||||
|
print("❌ GraphQL API returned errors:")
|
||||||
|
for error in result['errors']:
|
||||||
|
print(f" - {error.get('message')}")
|
||||||
|
return
|
||||||
|
|
||||||
|
creation_result = result.get('data', {}).get('pages', {}).get('create', {})
|
||||||
|
response_result = creation_result.get('responseResult', {})
|
||||||
|
|
||||||
|
if response_result.get('succeeded'):
|
||||||
|
page_info = creation_result.get('page', {})
|
||||||
|
print(f"✅ Success! Page '{page_info.get('title')}' created.")
|
||||||
|
print(f" - ID: {page_info.get('id')}")
|
||||||
|
print(f" - Path: {page_info.get('path')}")
|
||||||
|
print(f"🌐 View it at: {WIKI_URL}/{page_info.get('path')}")
|
||||||
|
else:
|
||||||
|
print(f"❌ API call failed: {response_result.get('message')}")
|
||||||
|
print(f" - Error Code: {response_result.get('errorCode')}")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"❌ An error occurred while connecting to the wiki: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Create a new page in Wiki.js.")
|
||||||
|
parser.add_argument('--path', required=True, help='The path for the new page (e.g., \"my-folder/my-page\").')
|
||||||
|
parser.add_argument('--title', required=True, help='The title of the new page.')
|
||||||
|
parser.add_argument('--content-file', required=True, help='Path to the markdown file containing the page content.')
|
||||||
|
parser.add_argument('--tags', nargs='*', help='A list of tags to add to the page.')
|
||||||
|
parser.add_argument('--locale', default='en', help='The locale for the page (default: en).')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
create_wiki_page(args.path, args.title, args.content_file, args.tags, args.locale)
|
||||||
|
|
||||||
133
scripts/edit_page.py
Normal file
133
scripts/edit_page.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import argparse
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
WIKI_URL = os.getenv("WIKI_URL")
|
||||||
|
API_KEY = os.getenv("WIKI_API_KEY")
|
||||||
|
|
||||||
|
# --- GraphQL Queries & Mutations ---
|
||||||
|
GRAPHQL_LIST_ALL_PAGES_QUERY = """
|
||||||
|
query AllPages {
|
||||||
|
pages {
|
||||||
|
list {
|
||||||
|
id
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
GRAPHQL_UPDATE_MUTATION = """
|
||||||
|
mutation UpdatePage($id: Int!, $content: String, $description: String, $tags: [String], $title: String) {
|
||||||
|
pages {
|
||||||
|
update(id: $id, content: $content, description: $description, tags: $tags, title: $title) {
|
||||||
|
responseResult { succeeded, errorCode, slug, message }
|
||||||
|
page { id, path, title }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_graphql_query(query, variables):
|
||||||
|
"""Helper function to run a GraphQL query."""
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {API_KEY}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = requests.post(f"{WIKI_URL}/graphql", headers=headers, json={"query": query, "variables": variables}, timeout=15)
|
||||||
|
response.raise_for_status()
|
||||||
|
result = response.json()
|
||||||
|
if 'errors' in result:
|
||||||
|
print("❌ GraphQL API returned errors:")
|
||||||
|
for error in result['errors']:
|
||||||
|
print(f" - {error.get('message')}")
|
||||||
|
return None
|
||||||
|
return result
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"❌ An error occurred: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_page_id(path_to_find, locale):
|
||||||
|
"""Get the ID of a page by fetching all pages and searching for its path."""
|
||||||
|
print("🔎 Fetching all pages to find the correct ID...")
|
||||||
|
result = run_graphql_query(GRAPHQL_LIST_ALL_PAGES_QUERY, {})
|
||||||
|
if not result:
|
||||||
|
print("❌ Failed to fetch the page list.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
all_pages = result.get('data', {}).get('pages', {}).get('list', [])
|
||||||
|
if not all_pages:
|
||||||
|
print("❌ No pages were found on the wiki.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for page in all_pages:
|
||||||
|
if page.get('path') == path_to_find:
|
||||||
|
page_id = page.get('id')
|
||||||
|
print(f"📄 Found page with ID: {page_id} for path: '{path_to_find}'")
|
||||||
|
return page_id
|
||||||
|
|
||||||
|
print(f"❌ A page with the path '{path_to_find}' was not found.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def edit_wiki_page(path, content_file, new_title, locale):
|
||||||
|
"""Updates an existing wiki page."""
|
||||||
|
if not all([API_KEY, WIKI_URL]):
|
||||||
|
print("❌ Error: WIKI_API_KEY and/or WIKI_URL not found in environment variables.")
|
||||||
|
return
|
||||||
|
|
||||||
|
page_id = get_page_id(path, locale)
|
||||||
|
if not page_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"id": page_id,
|
||||||
|
"description": f"Page at path {path} updated via script.",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
|
||||||
|
if content_file:
|
||||||
|
try:
|
||||||
|
with open(content_file, 'r') as f:
|
||||||
|
variables['content'] = f.read()
|
||||||
|
print(f"🔄 Preparing to update content from '{content_file}'...")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"❌ Error: Content file not found at '{content_file}'")
|
||||||
|
return
|
||||||
|
|
||||||
|
if new_title:
|
||||||
|
variables['title'] = new_title
|
||||||
|
print(f"🔄 Preparing to update title to '{new_title}'...")
|
||||||
|
|
||||||
|
if 'content' not in variables and 'title' not in variables:
|
||||||
|
print("✨ Nothing to update. Please provide a new title or content file.")
|
||||||
|
return
|
||||||
|
|
||||||
|
result = run_graphql_query(GRAPHQL_UPDATE_MUTATION, variables)
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
|
||||||
|
op_result = result.get('data', {}).get('pages', {}).get('update', {})
|
||||||
|
response_result = op_result.get('responseResult', {})
|
||||||
|
|
||||||
|
if response_result.get('succeeded'):
|
||||||
|
page_info = op_result.get('page', {})
|
||||||
|
print(f"✅ Success! Page '{page_info.get('title')}' updated.")
|
||||||
|
print(f"🌐 View it at: {WIKI_URL}/{page_info.get('path')}")
|
||||||
|
else:
|
||||||
|
print(f"❌ API call failed: {response_result.get('message')}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Edit an existing page in Wiki.js.")
|
||||||
|
parser.add_argument('--path', required=True, help='The path of the page to edit (e.g., \"home\").')
|
||||||
|
parser.add_argument('--content-file', help='Path to a markdown file with the new content.')
|
||||||
|
parser.add_argument('--title', help='A new title for the page.')
|
||||||
|
parser.add_argument('--locale', default='en', help='The locale of the page (default: en).')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
edit_wiki_page(args.path, args.content_file, args.title, args.locale)
|
||||||
Reference in New Issue
Block a user