日本語 | English
A self-hosted internal communication tool that operates entirely within an on-premises environment (closed network) with zero external internet connectivity. Designed for OSS release — all site-specific settings are externalized via environment variables.
The basic design document (architecture, class/ER diagrams, sequence diagrams, flowcharts) is published on GitHub Pages.
Diagrams are rendered offline using Mermaid. You can also open the HTML files under
docs/locally.
- Fully offline: No external CDN, cloud storage, or push notification servers. Emoji via Unicode; all assets served locally.
- Real-time communication: Bidirectional messaging over WebSocket.
- On-premises storage: All files are saved to a local path on the internal server.
- Near-zero external dependencies: Password hashing (PBKDF2) and token signing (HMAC) use Python's standard library only.
Note: Voice channels are not supported in the current phase. Text channels, DMs, and file sharing are provided.
| Category | Technology |
|---|---|
| Backend | Python 3.11+ / FastAPI |
| Real-time | WebSocket (FastAPI / uvicorn) |
| Database | SQLite (local, via SQLAlchemy) |
| Frontend | Vanilla JavaScript / CSS (no build step, no CDN) |
- Authentication: registration & login (own DB, passwords hashed with PBKDF2)
- Multiple accounts: hold multiple accounts on one device, switch with one click
- Profile: display name, icon, status message
- Servers (workspaces): create & list
- Invite-code-based joining: admins/moderators issue codes; only holders can join (configurable max uses & expiration)
- Role management: admin / moderator / member
- Text channels: create & browse
- Messages: send, edit, delete, replies (displayed below the parent message), emoji reactions
- Markdown: headings, bold, italic, strikethrough, code/code blocks (with filename display support), blockquotes, lists, tables, links (XSS-safe, zero-dependency custom implementation)
- Embedded diagrams: Mermaid (client-side) and PlantUML (server-side via
plantuml.jar, optional) - Mentions: notify via
@username - DMs: 1-on-1 / group
- File sharing: drag & drop supported. Upload/download images, PDFs, Office files, etc. (stored on-premises)
- File preview: HTML / Markdown / PlantUML files display inline previews in chat (with source toggle & open-in-new-tab). Images show thumbnails as before
- Notifications: unread badges, desktop notifications on mention (browser Notification API)
python -m pip install -r requirements.txtFor air-gapped environments, pre-download wheels with
pip downloadand install offline.
copy .env.example .envOpen .env and change at least SECRET_KEY to a strong random value.
python run.pyOpen http://localhost:8000 in your browser. The first user registers via "Sign Up".
Server participation uses invite codes — only those who know the code can join.
- Open the target server and click the "👤+ (Invite Member)" button above the channel list
- Optionally set "Max uses" and "Expiration (minutes)", then click "Generate Invite Code"
- The generated code (e.g.
WZ9273UQ) is auto-copied to clipboard — share it with the invitee - Issued codes are listed on this screen and can be revoked at any time
- Click the "+ (Add Server)" button on the left
- Enter the invite code — the target server name will appear
- Click "Join" to join as a
member
Only
admin/moderatorroles can issue invites. Direct join (/join) is disabled.
You can hold and switch between multiple accounts on a single device.
- Add account: bottom-left ⚙ (Profile Settings) → "Add another account" → Login / Register
- Switch: select from the account list in Profile Settings
- Logout: "Log out of this account" removes only that account; if another exists, it auto-switches
Account tokens are stored in the device's
localStorage. On shared devices, log out of each account after use.
Messages support Markdown formatting:
- Headings (
# – ######), bold (**bold**), italic (*italic*), strikethrough (~~strike~~) - Inline code (
`code`), code blocks (```lang ... ```) - Code block filename display: Use
```ruby:filename.rb(language followed by:filename) to show the filename above the code block - Blockquotes (
> quote), lists (bulleted/numbered), horizontal rule (---), tables (GFM), links ([text](URL))
All rendered with a custom zero-dependency implementation with HTML escaping and javascript: URL removal.
Specify mermaid as the code block language. The Mermaid library is bundled at
frontend/static/vendor/mermaid.min.js — no external CDN connection.
```mermaid
graph TD
A[Start] --> B{Condition}
B -->|Yes| C[Process 1]
B -->|No| D[Process 2]
```
Specify plantuml (or puml) as the code block language. Requires plantuml.jar
(Java). Set the path in .env via PLANTUML_JAR. If not configured, source code
is displayed as-is.
Java version requirement: PlantUML 1.2025+ requires Java 11 or later.
PLANTUML_JAR=./plantuml/plantuml-asl-1.2026.6.jar
JAVA_BIN=./runtime/jdk-17.0.19+10-jre/bin/java.exe
Portable JRE (Eclipse Temurin / Amazon Corretto zip) can be extracted to
runtime/and referenced viaJAVA_BIN.runtime/is in.gitignore— deploy per environment.
Drag and drop files onto the chat area to upload. Inline previews are displayed based on file type:
| File type | Preview |
|---|---|
| Images (png / jpg / gif etc.) | Thumbnail image |
| HTML (.html / .htm) | Rendered via iframe |
| Markdown (.md) | Rendered Markdown → HTML |
| PlantUML (.puml) | Rendered as SVG diagram |
| Others | 📎 link |
All previews include a </> source toggle button and a ↗ open in new tab link.
Preview width is resizable by dragging.
Message timestamps are controlled by TZ_OFFSET_HOURS in .env.
# Offset from UTC in hours. Japan: 9, UTC: 0.
TZ_OFFSET_HOURS=9
Default is 9 (JST). Change to the appropriate offset for other regions.
Set PEM certificate paths in .env to enable HTTPS/WSS:
SSL_CERTFILE=./certs/server.crt
SSL_KEYFILE=./certs/server.key
WebSocket connections automatically use wss:// when TLS is configured.
python -m pip install cryptography
python scripts/gen_cert.pyThis generates certs/server.crt and certs/server.key with SAN entries for
localhost, 127.0.0.1, the machine's hostname, and LAN IPv4 addresses.
With TLS enabled, certificates are managed automatically at startup
(TLS_AUTO_GENERATE=true by default):
- Generated if missing
- Self-signed certs are renewed when expiry is within
TLS_RENEW_DAYS(default: 30) - CA-issued certificates are never overwritten (warning only)
certs/contains private keys and is in.gitignore. Generate per environment.
Self-signed certificates trigger browser warnings. Options:
- Proceed via "Advanced → Continue to site"
- Import
certs/server.crtinto each device's trusted root certificate store - Use a certificate from an internal CA (recommended)
LocalChat/
├── run.py Startup script
├── requirements.txt Dependencies
├── .env.example Environment variable sample
├── backend/
│ ├── main.py FastAPI application
│ ├── config.py Environment variable loader
│ ├── database.py DB connection (SQLite/SQLAlchemy)
│ ├── models.py ORM models
│ ├── schemas.py I/O schemas
│ ├── security.py Password hash & token signing
│ ├── deps.py Auth dependency
│ ├── services.py Authorization & formatting utilities
│ ├── ws_manager.py WebSocket connection manager
│ └── routers/ API endpoint routers
│ ├── auth.py / users.py / servers.py
│ ├── invites.py Invite code issuance & joining
│ ├── messages.py / dms.py / files.py / ws.py
│ └── render.py PlantUML server-side rendering
└── frontend/
├── index.html
└── static/
├── css/style.css
├── js/app.js
├── js/markdown.js Markdown renderer (custom)
└── vendor/mermaid.min.js Mermaid (bundled locally)
- Database:
./data/localchat.db(configurable viaDATABASE_URL) - Uploaded files:
./data/uploads/(configurable viaUPLOAD_DIR)
To store on a NAS, point these paths to the mounted share.
- Always change
SECRET_KEYbefore production use. .envanddata/are in.gitignore. Never commit secrets.- Enabling TLS (HTTPS/WSS) is recommended even within the LAN.
To ensure a fully closed network, access from non-private IPs is rejected by default. Defense in depth:
- Application layer (enabled by default): Rejects clients whose IP is not in private ranges (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, loopback, link-local, IPv6 ULA). HTTP returns 403; WebSocket closes before handshake. - OS layer (firewall):
scripts/open_firewall.ps1allows only Domain/Private profiles, not Public. - Bind scope (optional): Set
HOSTin.envto a specific LAN IP instead of0.0.0.0.
RESTRICT_TO_PRIVATE=true
ALLOWED_CIDRS=
TRUST_FORWARDED_FOR=false
Behind a reverse proxy, set
TRUST_FORWARDED_FOR=trueso the app inspectsX-Forwarded-For.
This project is released under the MIT License.
You are free to use, modify, and distribute it, provided the copyright and
permission notice are retained. See LICENSE for details.
Copyright (c) 2026 tkwork
This application is designed for internal use within a closed corporate LAN. Under such usage, registration or notification under Japan's Telecommunications Business Act is generally not required.
However, if you operate this system in a manner that involves communication with external third parties (e.g., providing the service to partner companies, publishing on the internet, or offering it as a SaaS), it may constitute a telecommunications business and require prior notification or registration with the Ministry of Internal Affairs and Communications (MIC).
| Usage | Applicability | Procedure |
|---|---|---|
| Internal use only (closed corporate LAN) | Likely not applicable | Generally none |
| Provided to affiliates / business partners | May be applicable | Confirm notification requirement |
| Offered to the public as SaaS, etc. | Likely applicable | Registration or notification required |
If you change the deployment to involve external communication, consult with the relevant Regional Bureau of Telecommunications before starting operation.
For detailed legal analysis, see law.md (Japanese). This notice is general information, not legal advice. Consult a legal professional for definitive guidance.