Single-process Awiki Community Server MVP. It provides DID registration, public DID documents, profile APIs, Markdown Pages, plaintext direct messaging, group participant APIs, local attachment storage, sync/read-state, and a public /anp-im/rpc entry for cross-domain direct calls.
This server implements those capabilities itself. It is not a proxy to awiki.info, and it does not require awiki.info or sibling AWiki services to run.
The Community edition deliberately does not implement group creation or management, Direct/Group E2EE, federation peer routes, relay, remote projection, billing, production identity providers, email or phone verification flows, Aliyun integrations, or multi-tenant hosting.
Application code lives under src/awiki_open_server/. protocol/anp_adapter.py
is the only ANP Python SDK adapter and requires anp==0.8.8 at runtime;
service_identity.py uses it for HTTP Signatures, Content-Digest, and origin
proof checks. app/ owns FastAPI settings, route mounting, and realtime wiring.
messaging/ owns direct messaging, group participant methods, local sync, and
read-state handlers. attachments/ owns local upload slots, committed objects,
and download tickets. user_compat/ implements the local User Service
compatibility surface. shared/runtime.py contains cross-cutting DID discovery,
HTTP JSON, signature, object URL, and realtime helpers. services.py is now a
compatibility facade plus the remaining content/site/DID relationship handlers;
new domain logic should go into the domain package rather than back into
services.py.
Install dependencies in your preferred Python environment:
python3 -m pip install -e '.[dev]'The dependency set pins ANP Python SDK anp==0.8.8; importing
awiki_open_server.protocol.anp_adapter fails fast if another SDK version is
loaded. In this workspace, local verification can use the sibling SDK checkout
with PYTHONPATH=../anp/anp:src when the active environment still has an older
installed anp package.
Start the server:
PYTHONPATH=src \
AWIKI_DATA_DIR=.awiki-open-server \
AWIKI_PUBLIC_BASE_URL=http://127.0.0.1:8765 \
AWIKI_DID_DOMAIN=localhost \
python3 -m uvicorn 'awiki_open_server.app.main:create_app' \
--factory --host 127.0.0.1 --port 8765Useful configuration:
| Variable | Default | Purpose |
|---|---|---|
AWIKI_DATA_DIR |
.awiki-open-server |
SQLite database and object files. |
AWIKI_PUBLIC_BASE_URL |
http://127.0.0.1:8000 |
Public base used in DID docs and object URLs. |
AWIKI_DID_DOMAIN |
localhost |
Default DID and handle domain. |
AWIKI_SERVICE_DID |
did:wba:<domain> |
Service DID advertised in ANPMessageService. |
AWIKI_SERVICE_PRIVATE_KEY_PEM |
unset | Ed25519 PKCS#8 PEM used to sign service-to-service HTTP requests. Use \n escapes when passing through env files. |
AWIKI_SERVICE_PRIVATE_KEY_PATH |
unset | File path for the same Ed25519 private key. Prefer this for local deployment. |
AWIKI_SERVICE_DID_DOCUMENT_JSON |
generated | Optional fixed service DID document. If omitted, the server generates one from the service private key. |
AWIKI_IM_RPC_PATH |
/im/rpc |
Local client JSON-RPC path. |
AWIKI_ANP_PUBLIC_RPC_PATH |
/anp-im/rpc |
Public ANP RPC path. |
AWIKI_WS_PATH |
/im/ws |
Local WebSocket notification path. |
AWIKI_OBJECT_UPLOAD_PATH |
/objects/upload |
Local object upload path prefix. |
AWIKI_OBJECT_DOWNLOAD_PATH |
/objects |
Local object download path prefix. |
AWIKI_ALLOW_UNSIGNED_PEER_DEV |
false |
Allows unsigned /anp-im/rpc direct.send only for local development tests. Do not enable for real interop. |
AWIKI_DID_RESOLVER_BASE_URLS |
unset | Optional development resolver map such as source.test=http://127.0.0.1:9001,target.test=http://127.0.0.1:9002 or a JSON object. Leave unset in normal public deployment. |
AWIKI_DID_VERIFY_DEV_CODE |
666666 |
Local /did-verify/rpc login dev code. Falls back to DEV_BYPASS_CODE if set. |
AWIKI_ENABLE_CONTACT_VERIFICATION_COMPAT |
false |
Enables legacy local phone/email verification shims for old client tests. Keep disabled for the MVP and public deployment. |
AWIKI_CONTACT_VERIFICATION_DEV_OTP |
123456 |
Local compatibility OTP used only when contact verification compatibility is explicitly enabled. |
Do not commit .awiki-open-server/, SQLite databases, object files, .env, or real tokens.
For real cross-domain direct interop, configure a stable service DID and private key:
AWIKI_PUBLIC_BASE_URL=https://rwiki.cn
AWIKI_DID_DOMAIN=rwiki.cn
AWIKI_SERVICE_DID=did:wba:rwiki.cn
AWIKI_SERVICE_PRIVATE_KEY_PATH=/secure/path/rwiki-service-ed25519.pem
AWIKI_ENABLE_CONTACT_VERIFICATION_COMPAT=falseThe published https://rwiki.cn/.well-known/did.json must be served by this process and contain the matching verificationMethod, authentication, proof, and exactly one public ANPMessageService. Outbound remote direct requests require the client/CLI ANP envelope with auth.origin_proof; the server forwards that proof unchanged and signs the HTTP hop with AWIKI_SERVICE_DID.
Example deployment templates are in deploy/. They show how to run Uvicorn on
localhost and publish https://rwiki.cn through nginx. The important boundary
is that rwiki.cn must proxy to this process, not to awiki.info,
user-service, or message-service.
Run the test suite:
PYTHONPATH=src python3 -m pytest tests -qFocused suites:
- ANP SDK/signature adapter:
tests/test_protocol_anp_sdk.py - Route and env path config:
tests/test_route_config.py - User Service compatibility:
tests/test_user_service_compat.py,tests/test_identity_documents.py,tests/test_contact_auth_compat.py,tests/test_profile_compat.py,tests/test_agent_compat.py,tests/test_site_relationships.py - Messaging surface:
tests/test_messaging_surface.py,tests/test_direct_messages.py,tests/test_group_participant.py,tests/test_attachments.py,tests/test_sync_read_state.py - Public deployment gate:
tests/test_rwiki_cn_system.py, skipped unlessAWIKI_RUN_PUBLIC_SYSTEM_TESTS=1
Run an in-process CLI smoke without starting Uvicorn:
PYTHONPATH=src python3 scripts/awiki_open_cli.py smoke-asgi \
--data-dir /tmp/awiki-open-server-cli-asgiRun a local HTTP smoke against a running server:
PYTHONPATH=src python3 scripts/awiki_open_cli.py smoke-local \
--base-url http://127.0.0.1:8765 \
--did-domain localhostRun a local two-server cross-domain smoke:
PYTHONPATH=src python3 scripts/awiki_open_cli.py smoke-cross-domain-local \
--data-root /tmp/awiki-open-server-cross-domain-local --cleanThis starts two independent Uvicorn processes with separate SQLite stores,
service DIDs, and Ed25519 service keys. It uses AWIKI_DID_RESOLVER_BASE_URLS
only to map test DID domains to loopback ports, then verifies DID discovery,
client auth.origin_proof, service-to-service HTTP Signature, signed
/anp-im/rpc direct.send, and bidirectional inbox delivery. This is a local
protocol gate; it does not replace the public rwiki.cn to awiki.info
interoperability gate.
Check that a public deployment is really serving this repository:
PYTHONPATH=src python3 scripts/awiki_open_cli.py verify-public \
--base-url https://rwiki.cn \
--did-domain rwiki.cnverify-public must pass before using rwiki.cn for real awiki.info
interoperability tests. A 404 for /.well-known/did.json, /healthz, or
/anp-im/rpc means the public domain is not yet routed to this server.
Run the guarded public system tests against rwiki.cn:
AWIKI_RUN_PUBLIC_SYSTEM_TESTS=1 \
PYTHONPATH=src python3 -m pytest tests/test_rwiki_cn_system.py -qThese tests are skipped by default in local and CI runs. When enabled, they
verify the public service DID document, capabilities, disabled contact
verification, DID registration, direct inbox/history, and the open group
participant boundary on https://rwiki.cn.
The existing Rust CLI can also connect to this server. Use an isolated awiki-cli-rs2 worktree and temporary CLI workspace so validation does not write into a developer's active CLI checkout:
CARGO_TARGET_DIR=/tmp/awiki-cli-rs2-open-server-target \
cargo build -p awiki-cli --bin awiki-cli --locked
AWIKI_CLI_WORKSPACE_HOME_DIR=/tmp/awiki-cli-open-server-workspace \
/tmp/awiki-cli-rs2-open-server-target/debug/awiki-cli \
id register --handle cli-alice --phone 13800138000 --otp 123456The current Rust CLI still requires either --phone or --email on
id register. In this MVP those CLI arguments are placeholders for the
existing CLI command shape: the server-side did-auth.register path does not
send SMS, send email, call Aliyun, or persist phone/email verification state.
For a repeatable local Rust CLI compatibility gate, let this repository start its own temporary server and create two isolated CLI workspaces:
PYTHONPATH=src python3 scripts/awiki_open_cli.py smoke-rust-cli-local \
--awiki-cli-bin /tmp/awiki-cli-rs2-open-server-target/debug/awiki-cli \
--data-root /tmp/awiki-open-server-rust-cli-local --cleanThis verifies current Rust CLI registration through the local DID registration path,
direct send/inbox/history, group join/send/messages, people
follow/status/following/followers, and site root/page commands against this
server. It is a local User Service / Message Service compatibility gate; it
does not replace the public rwiki.cn to awiki.info interoperability gate.
The server exposes compatibility routes used by the Rust CLI, including /user-service/did-auth/rpc, /user-service/did/profile/rpc, and /user-service/handle/rpc. These routes are implemented locally by this repository and do not forward to an external User Service.
It also exposes minimal /user-service/agent-registration/rpc,
/user-service/message-agent/rpc, and /user-service/agent-inventory/rpc
compatibility routes for current daemon status, controller scope, sender
checks, invocation authorization, archive, and local policy fields. These cover
local one-time agent registration tokens, message-agent binding state, and
daemon compatibility only; they do not implement hosted runtime orchestration,
delegated secret management, or a production policy engine.
The MVP does not use phone or email verification. Legacy contact-verification
routes such as /auth/sms, /auth/sms-codes, /auth/email-send,
/auth/email-status, and phone-bind routes return
contact_verification_not_enabled by default. They can be enabled only for
local compatibility tests with AWIKI_ENABLE_CONTACT_VERIFICATION_COMPAT=true;
even then they remain in-process dev shims and never call SMS, email, Aliyun,
awiki.info, User Service, or Message Service. Token and WebSocket ticket
verification routes remain local compatibility helpers and return X-User-Id
/ X-DID headers for nginx auth_request integrations.
For User Service DID verification compatibility, /did-verify/rpc and
/user-service/did-verify/rpc expose send_code, login, and refresh.
This is a local dev provider: send_code does not call an external message
service, login accepts the DID verify dev code 666666 by default, and
tokens are the local Community tokens already stored for registered DIDs.
Override the DID verify code with AWIKI_DID_VERIFY_DEV_CODE or
DEV_BYPASS_CODE. This is DID verify compatibility only and does not enable
phone or email verification.
DID Auth supports local revoke for registered DIDs. Revocation marks the
local user and DID document inactive; the same token or DID string can no
longer pass token verification, DID verify login/refresh, WebSocket ticket
verification, get_me, or update_document. The profile and historical
message data remain stored, but the active DID document route returns 404.
replace_did and recover_handle remain unsupported in the Community server.
For older User Service profile clients, the server also exposes local profile
compatibility routes: /me, /me/rpc, /profiles/{user_id},
/user-service/profiles/{user_id}, /users/{user_id}/profile, and
/users/rpc. In this Community server, user_id is the local DID, and profile
fields are mapped onto the local DID profile record. These routes do not call
an external User Service.
Remote capability diagnostics can call the online awiki.info service, but awiki.info is a remote peer for interoperability testing, not this server's backend:
PYTHONPATH=src python3 scripts/awiki_open_cli.py smoke-awiki-info \
--base-url https://awiki.info \
--did-domain rwiki.cnTo send a direct test message, also provide --token, --sender-did, --recipient-did, and --origin-proof-json. Remote capability uses ANP JSON-RPC params.meta/body; remote direct uses params.meta/auth/body. A missing params.meta response means the request shape is wrong and is not a passing remote check.
For real interoperability validation, run this server on its own reachable base URL with a service private key configured, and configure the Rust CLI for that URL. The remote side should be an existing or test user on awiki.info. A valid test proves both directions:
- local open-server user ->
awiki.infouser: this server resolves the remote DID document, preserves the CLIauth.origin_proof, signs the HTTP hop asAWIKI_SERVICE_DID, and POSTsdirect.sendto the remoteANPMessageService.serviceEndpoint. awiki.infouser -> local open-server user:awiki.inforesolves this server's DID document and POSTs signeddirect.sendto this server's/anp-im/rpc.
Do not treat two CLI workspaces that both point at https://awiki.info as validation for this repository; that only verifies the online service itself.
Core and compatibility routes. Contact-verification compatibility routes are
mounted for old clients but return contact_verification_not_enabled unless
AWIKI_ENABLE_CONTACT_VERIFICATION_COMPAT=true is explicitly set for local
testing:
GET /healthzGET /healthGET /user-service/healthGET /im/healthzPOST /did-auth/rpcPOST /user-service/did-auth/rpcPOST /did-verify/rpcPOST /user-service/did-verify/rpcPOST /did/profile/rpcPOST /user-service/did/profile/rpcGET /mePATCH /mePOST /me/rpcGET /user-service/mePATCH /user-service/mePOST /user-service/me/rpcGET /profiles/{user_id}GET /user-service/profiles/{user_id}GET /users/{user_id}/profileGET /user-service/users/{user_id}/profilePOST /users/rpcPOST /user-service/users/rpcPOST /handle/rpcPOST /user-service/handle/rpcPOST /did/relationships/rpcPOST /user-service/did/relationships/rpcPOST /user-service/agent-registration/rpcPOST /user-service/agent-inventory/rpcPOST /user-service/message-agent/rpcPOST /auth/sms-codesPOST /user-service/auth/sms-codesPOST /auth/smsPOST /user-service/auth/smsPOST /auth/email-sendPOST /user-service/auth/email-sendGET /auth/email-statusGET /user-service/auth/email-statusPOST /auth/phone-bind-sendPOST /user-service/auth/phone-bind-sendPOST /auth/phone-bind-verifyPOST /user-service/auth/phone-bind-verifyPOST /auth/token-refreshPOST /user-service/auth/token-refreshGET /auth/token-verifyGET /user-service/auth/token-verifyGET /auth/verifyGET /user-service/auth/verifyGET /sessions/verifyGET /user-service/sessions/verifyPOST /ws/ticketsPOST /user-service/ws/ticketsGET /ws/tickets/verifyGET /user-service/ws/tickets/verifyGET /auth/ws-ticket/verifyGET /user-service/auth/ws-ticket/verifyPOST /content/rpcPOST /user-service/content/rpcGET /content/{slug}.mdPOST /site/rpcGET /GET /pages/{slug}.mdPOST /im/rpcby default, orAWIKI_IM_RPC_PATHPOST /anp-im/rpcby default, orAWIKI_ANP_PUBLIC_RPC_PATHPUT /objects/upload/{slot_id}by default, orAWIKI_OBJECT_UPLOAD_PATH/{slot_id}GET /objects/{object_id}by default, orAWIKI_OBJECT_DOWNLOAD_PATH/{object_id}GET /.well-known/did.jsonGET /dids/resolve/{sub_path}/did.jsonGET /{sub_path}/did.json
/im/rpc is the local client entry and exposes inbox, history, sync, read-state, group participant, and attachment control methods. /anp-im/rpc is the public cross-domain entry and only exposes anp.get_capabilities, direct.send, group.get_info, group.join, and attachment.get_download_ticket. Public direct.send and group.join require a business auth.origin_proof and a service-to-service HTTP Signature unless AWIKI_ALLOW_UNSIGNED_PEER_DEV is enabled for local tests.
Direct and group messages preserve Message Service payload shapes. text/plain
uses body.text; application/json and
application/anp-attachment-manifest+json use body.payload as a JSON object;
other non-text content types use body.payload_b64u. The server rejects
ANP-envelope messages whose body fields do not match meta.content_type, while
keeping older flat text CLI calls compatible.
Local message views (inbox.get, direct.get_history, group.list_messages,
and sync.thread_after) project those stored bodies with Message Service
semantics: text messages return type=text, JSON payloads return type=json,
attachment manifests return type=attachment_manifest, and other non-text
payloads return type=binary. The original body and content_type are still
included for clients that need the raw ANP shape.
Direct inbox read state follows the Message Service compatibility split:
inbox.mark_read marks the current user's visible direct message ids in
direct_message_views.read_at, default inbox.get returns unread messages
only, and inbox.get {"include_read": true} or direct.get_history can show
read messages with is_read and read_at. read_state.mark_read remains a
thread watermark API and does not emit account-level sync events.
Local view methods accept the Message Service params.meta/body shape and
older flat params. When present, meta.sender_did and body.user_did must
match the authenticated local DID. inbox.get supports skip and limit;
direct.get_history supports peer_did, since_seq/since, skip, and
limit. The deprecated direct-history group_did path is rejected; use
group.list_messages for group history.
The server recognizes only the documented daemon liveness heartbeat payload
(application/json, body.payload.schema = awiki.agent.status.v1,
status_scope = daemon, message = daemon heartbeat) as no-store. It returns
delivery_state = ephemeral and may notify an online recipient, but it does not
write that heartbeat to inbox, history, sync events, or sender-side history.
Other daemon/App status payloads, including run and snapshot statuses, remain
durable messages.
Supported group methods are participant-only: group.get_info, group.join,
group.leave, group.send, group.get, group.list, group.list_members,
and group.list_messages. group.get_info exposes minimal existing-group
information for discovery and open join. Member list, group messages, leave,
and send require the current DID to be a group member. Management methods such
as group.create, group.add, group.remove, group.update_profile, and
group.update_policy return not_supported. The seeded open-join group DID
follows AWIKI_DID_DOMAIN, for example did:wba:localhost:groups:open
locally and did:wba:rwiki.cn:groups:open in public deployment.
Group local views accept the Message Service params.meta/body shape, validate
the optional local owner fields, and support limit, skip, and since_seq
pagination for group.list_messages. sync.thread_after applies the same
member check for group threads, so it cannot be used to read a group after
leaving it.
/im/ws accepts a local ticket from /ws/tickets or /user-service/ws/tickets. It keeps the connection open, sends an initial sync hint, and then publishes in-process realtime notifications for local direct and group participant activity: direct.incoming, group.incoming, and group.state_changed. This is a single-process Community runtime feature; multi-process fanout, external pub/sub, offline push, presence, typing indicators, and HA realtime delivery are not implemented. Clients should still use sync.delta and sync.thread_after as the durable recovery path.
Attachment upload slots return both a legacy upload_token and upload_headers. The data plane accepts either PUT /objects/upload/{slot_id}?token=... or the returned X-ANP-Upload-Token header. attachment.get_download_ticket accepts the local object_id owner flow and the Message Service ANP body shape with object_uri, attachment_id, requester_did, sender_did, message_id, message_security_profile, and either message_target_did or group_did. Responses include both legacy ticket/download_uri fields and download_ticket_b64u/ticket_binding. GET /objects/{object_id} accepts ?ticket=... and Authorization: Bearer <download_ticket>. This Community server only issues tickets for locally committed objects and local direct/group message context; it does not implement cross-domain attachment upload delegation, full attachment_access_grants, object E2EE authorization, or remote object relay.
/did/relationships/rpc and /user-service/did/relationships/rpc provide the minimal local DID relationship methods used by current CLI directory flows: follow, unfollow, get_following, get_followers, and get_status. They only operate on users registered in this server's configured DID domain.
/site/rpc provides a small Markdown site compatibility surface for the configured local domain: get_root, set_root, list_pages, get_page, create_page, update_page, rename_page, and delete_page. Public GET / and GET /pages/{slug}.md return raw Markdown. This is not production tenant hosting; cross-domain site management, templates, SEO rendering, and tenant admin policy are outside the Community MVP.