From d7b0f6fee0c6ad754dc96fc475ed29cbdd8d880b Mon Sep 17 00:00:00 2001 From: Aodhan Collins Date: Mon, 8 Sep 2025 02:22:20 +0100 Subject: [PATCH] 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. --- .env | 13 --- .gitignore | 18 ++++ README.md | 202 +++++++++++++++-------------------------- requirements.txt | 2 + scripts/create_page.py | 101 +++++++++++++++++++++ scripts/edit_page.py | 133 +++++++++++++++++++++++++++ 6 files changed, 329 insertions(+), 140 deletions(-) delete mode 100644 .env create mode 100644 .gitignore create mode 100644 requirements.txt create mode 100644 scripts/create_page.py create mode 100644 scripts/edit_page.py diff --git a/.env b/.env deleted file mode 100644 index 185d9a7..0000000 --- a/.env +++ /dev/null @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9ba615 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Environment variables +.env + +# Python +venv/ +__pycache__/ +*.pyc + +# Backups +backups/ + +# Schema file +introspectionSchema.json + +# IDE / OS specific +.vscode/ +.idea/ +.DS_Store diff --git a/README.md b/README.md index 2835ac4..fe6d5ff 100644 --- a/README.md +++ b/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 -1. **Run the setup script:** - ```bash - ./setup.sh - ``` +1. **Set Up Environment**: Ensure you have `docker` and `docker-compose` installed. The `setup.sh` script will check for these. -2. **Access your wiki:** - - Open http://localhost:3000 in your browser - - Complete the initial setup wizard - - Create your administrator account +2. **Run Setup Script**: This script generates a secure database password, creates necessary directories, and starts the wiki. + ```bash + ./setup.sh + ``` + +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. ## 📁 Project Structure ``` wikiaodh/ ├── docker-compose.yml # Docker services configuration -├── .env # Environment variables (passwords, etc.) -├── setup.sh # Automated setup script -├── README.md # This file -└── data/ # Created automatically for persistent data - ├── wiki/ # Wiki application data - ├── db/ # PostgreSQL database files - └── assets/ # Uploaded media and assets +├── .env # Environment variables (passwords, API key, etc.) +├── setup.sh # Automated setup script +├── backup.sh # Backup script for database and assets +├── README.md # This file +├── requirements.txt # Python dependencies for scripts +├── venv/ # Python virtual environment +└── scripts/ + ├── 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) -- `DB_PASSWORD`: Secure password for PostgreSQL database -- `WIKI_URL`: The URL where your wiki will be accessible -- `TZ`: Your timezone (default: Europe/London) +### Docker Commands -### Ports -- **3000**: Wiki.js web interface (http://localhost:3000) -- **5432**: PostgreSQL database (internal only) +- **Start the wiki**: `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` -## 📋 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 -# Start the wiki -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 +python3 scripts/create_page.py --path "my-folder/my-new-page" --title "My New Page" --content-file "path/to/my-content.md" ``` -## 💾 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 -# Backup database -docker-compose exec db pg_dump -U wikijs wiki > backup_$(date +%Y%m%d).sql +# Update content +python3 scripts/edit_page.py --path "home" --content-file "new_content.md" -# Backup uploaded files -tar -czf assets_backup_$(date +%Y%m%d).tar.gz data/assets/ +# Update title +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 -```bash -# Restore database -docker-compose exec -T db psql -U wikijs wiki < backup_YYYYMMDD.sql - -# 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/) +**Arguments**: +- `--path` (required): The path of the page to edit. +- `--content-file` (optional): Path to a markdown file with the new content. +- `--title` (optional): A new title for the page. --- -**Enjoy your personal wiki! 📖✨** +**Enjoy your powerful, scriptable personal wiki! 📖✨** diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a830e70 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +python-dotenv +requests diff --git a/scripts/create_page.py b/scripts/create_page.py new file mode 100644 index 0000000..327fbf8 --- /dev/null +++ b/scripts/create_page.py @@ -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) + diff --git a/scripts/edit_page.py b/scripts/edit_page.py new file mode 100644 index 0000000..76423fb --- /dev/null +++ b/scripts/edit_page.py @@ -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)