diff --git a/.gitignore b/.gitignore index 3887c73..6a71d16 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ apogee apogee.debug /*.log /*.json +obj/ diff --git a/.gitmodules b/.gitmodules index ea3b9b1..befc360 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "src/util"] path = src/util url = https://gitea.polaris-1.work/teknomunk/util.git +[submodule "assets/Soapbox"] + path = assets/soapbox + url = https://gitea.polaris-1.work/teknomunk/Soapbox.git diff --git a/README.md b/README.md index 9a47167..4455fd3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,41 @@ USE AT YOUR OWN RISK. * ActivityPub federation - follow interesting people on the many instances of the fediverse. # Dependencies +## Runtime * libCURL * OpenSSL - +* Globaly resolvable domain name +* Tor + +## Source Install/Development +* C Compiler (test with GCC) +* Ruby (hopefully temporary) + +# Source Installation Instructions + +Ensure you have system packages for the dependencies installed, then run the following code in +a terminal window: + +```` +git clone https://gitea.polaris-1.work/teknomunk/apogee.git +cd apogee +./build.sh +./apogee --listen=$PORT +```` + +If everything worked correctly, you should see something similar to the following: + +```` +Starting Apogee ActivityPub server... +Using port 21630 for web server +Starting section 6 (tor) +Starting section 0 (webserver) +Starting section 1 (inbox) +Starting section 2 (outbox) +Starting section 5 (fetch) +```` + +The listening port is chosen at random each time the program is run until setup, then the configured +port is used. Open a browser and navigate to http://${hostnameOrIPAddress}:${PORT}/, then fill in the +required information. diff --git a/assets/soapbox b/assets/soapbox new file mode 160000 index 0000000..c667899 --- /dev/null +++ b/assets/soapbox @@ -0,0 +1 @@ +Subproject commit c667899bd898056810acfe349da59491198fbb1f diff --git a/build.sh b/build.sh index 967571a..4495fd2 100755 --- a/build.sh +++ b/build.sh @@ -2,6 +2,8 @@ set -e +git submodule update --init --recursive + rm debug release 2>/dev/null || true find src | grep -E "\.template$" | while read FILE; do ruby tools/builder/tools/embed.rb "$FILE" diff --git a/src/controller/admin.c b/src/controller/admin.c new file mode 100644 index 0000000..75b46e7 --- /dev/null +++ b/src/controller/admin.c @@ -0,0 +1,191 @@ +#include "controller/admin.h" + +// Model +#include "src/model/server.h" +#include "src/model/owner.h" +#include "src/model/account.h" +#include "src/model/crypto/keys.h" + +// View + +// Controller +#include "src/controller/api/client_apps.h" + +// Submodules +#include "form.h" +#include "format.h" +#include "http/server/request.h" +#include "ffdb/fs_list.h" + +// Platform Headers +#include +#include + +const char* view_checkbox( bool value ) +{ + return value ? "checked" : ""; +} + +bool route_admin_request( struct http_request* req ) +{ + // TODO: authenticate + if( !check_authentication_header(req) ) { + printf( "User-Agent: %s\n", http_request_get_header(req,"user-agent") ); + + http_request_send_headers( req, 401, "text/plain", true ); + FILE* f = http_request_get_response_body( req ); + fprintf( f, "Not authorized to use this endpoint.\n" ); + return true; + } + + if( http_request_route_term( req, "/server-setup" ) ) { + return handle_admin_server_setup(req); + } + return false; +} + +// Special: /, step=1 +bool handle_admin_initial_owner_setup( struct http_request* req ) +{ + if( http_request_route_method( req, "POST" ) ) { + // TODO: handle post + FILE* body = http_request_get_request_data(req); + struct form_parser* fp = form_pull_parser_new(body); + if( !fp ) { goto show_owner_setup; } + + struct owner* o = owner_new(); + + // Create owner account + struct account* owner = account_new(); + owner->id = owner_account_id; + owner->server = strdup(g_server->domain); + owner->account_url = aformat("https://%s/owner/actor", g_server->domain ); + owner->banner = aformat("https://%s/owner/banner.blob", g_server->domain ); + owner->avatar.url = aformat("https://%s/owner/avatar.blob", g_server->domain ); + owner->avatar.static_url = aformat("https://%s/owner/avatar.blob", g_server->domain ); + owner->note = strdup(""); + account_save(owner); + + // Create home timeline account + { + struct account* home = account_new(); + home->id = home_timeline_id; + home->handle = strdup("%home-timeline"); + home->server = strdup("localhost"); + account_save(home); + account_free(home); + } + + // Create public timeline account + { + struct account* public = account_new(); + public->id = public_timeline_id; + public->handle = strdup("%public-timeline"); + public->server = strdup("localhost"); + account_save(public); + account_free(public); + } + + fs_list_set( "data/accounts/HEAD", 3 ); + + // Create RSA public/private keys + struct crypto_keys* keys = crypto_keys_new(); + crypt_keys_generate(keys); + crypto_keys_save_public(keys,"data/owner/public.pem"); + crypto_keys_save_private(keys,"data/owner/private.pem"); + crypto_keys_free(keys); + + bool success = false; + char* password = NULL; + char* confirm = NULL; + + char* key = NULL; + while( (key=form_pull_parser_read_key(fp)) ) { + if( 0 == strcmp(key,"password") ) { + password = strdup(form_pull_parser_read_value(fp)); + } else if( 0 == strcmp(key,"confirm") ) { + confirm = strdup(form_pull_parser_read_value(fp)); + } else if( 0 == strcmp(key,"handle") ) { + owner->handle = strdup(form_pull_parser_read_value(fp)); + owner->display_name = strdup(owner->handle); + account_save(owner); + } + } + + if( owner->handle && *owner->handle && password && confirm && ( 0 == strcmp(password,confirm) ) ) { + owner_set_password( o, password ); + success = true; + } + form_pull_parser_release(fp); + + if( success ) { + owner_save(o); + } + owner_free(o); + account_free(owner); + + if( success ) { + // TODO: generate crypto keys + + // Advance wizard to next step + g_server->configured = true; + app_args_save(); + + http_request_begin_send_headers( req, 302, false ); + http_request_send_header( req, "Location", "/?complete" ); + http_request_end_send_headers( req, false ); + return true; + } + } + +show_owner_setup: + http_request_send_headers( req, 200, "text/html", true ); + FILE* f = http_request_get_response_body( req ); + #include "view/admin/owner-setup.html.inc" + return true; +} + +// Route: /admin/server-setup +// Special: /, step=0 (when server hasn't been configured) +bool handle_admin_server_setup( struct http_request* req ) +{ + if( http_request_route_method( req, "POST" ) ) { + // TODO: handle post + FILE* body = http_request_get_request_data(req); + struct form_parser* fp = form_pull_parser_new(body); + if( !fp ) { return false; } + + app_args_load_from_form( g_server, fp ); + form_pull_parser_release(fp); + + // Advance wizard to next step + if( g_server->setup_wizard_step == 0 ) { + g_server->setup_wizard_step = 1; + } + + app_args_save(); + app_args_load(); + + // Redirect + http_request_begin_send_headers( req, 302, false ); + http_request_send_header( req, "Location", "/?account" ); + http_request_end_send_headers( req, false ); + return true; + } else { + http_request_send_headers( req, 200, "text/html", true ); + FILE* f = http_request_get_response_body( req ); + #include "view/admin/server-setup.html.inc" + return true; + } +} + +bool handle_admin_server_setup_wizard( struct http_request* req ) +{ + switch(g_server->setup_wizard_step) { + case 0: return handle_admin_server_setup(req); + case 1: return handle_admin_initial_owner_setup(req); + } + + return false; +} + diff --git a/src/controller/admin.h b/src/controller/admin.h new file mode 100644 index 0000000..9f8ce11 --- /dev/null +++ b/src/controller/admin.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +struct http_request; + +bool route_admin_request( struct http_request* req ); +bool handle_admin_server_setup( struct http_request* req ); +bool handle_admin_server_setup_wizard( struct http_request* req ); + diff --git a/src/controller/api/client_apps.c b/src/controller/api/client_apps.c index 0c67a53..5267659 100644 --- a/src/controller/api/client_apps.c +++ b/src/controller/api/client_apps.c @@ -92,6 +92,8 @@ bool check_bearer_token( const char* auth_token ) client_app_free(app); return false; } + app->last_used = time(NULL); + client_app_save(app); client_app_free(app); return true; diff --git a/src/controller/inbox.c b/src/controller/inbox.c index da321e1..0ef7f69 100644 --- a/src/controller/inbox.c +++ b/src/controller/inbox.c @@ -27,6 +27,7 @@ #include #include #include +#include extern bool terminate; @@ -697,6 +698,8 @@ bool cleanup_box( const char* box ) void process_inbox() { + mkdir( "data/inbox", 0750 ); + while( !terminate ) { bool activity = false; activity |= process_one(); diff --git a/src/controller/main.c b/src/controller/main.c index 316960c..7394cf6 100644 --- a/src/controller/main.c +++ b/src/controller/main.c @@ -17,6 +17,7 @@ #include "controller/inbox.h" #include "controller/activity_pub.h" #include "controller/api/emoji.h" +#include "controller/admin.h" // Standard Library #include @@ -169,6 +170,9 @@ bool route_request( struct http_request* req ) char onion_location[512]; snprintf( onion_location,512, "http://%s%s", g_server->tor_hidden_service, http_request_get_full_path(req) ); http_request_add_header( req, "Onion-Location", onion_location ); + #define SOURCE_URL "https://gitea.polaris-1.work/teknomunk/apogee" + #define LICENSE_URL "https://www.gnu.org/licenses/agpl-3.0.html" + http_request_add_header( req, "Link", "<" LICENSE_URL ">; rel=\"license\", <" SOURCE_URL ">; rel=\"source\"" ); bool inner( struct http_request* req ) { if( http_request_route( req, "/oauth" ) ) { @@ -196,6 +200,13 @@ bool route_request( struct http_request* req ) } } else if( http_request_route( req, "/nodeinfo" ) ) { return route_nodeinfo(req); + } else if( http_request_route_term( req, "/instance/soapbox.json" ) ) { + http_request_send_headers( req, 200, "application/json", true ); + FILE* f = http_request_get_response_body( req ); + + #include "view/soapbox.json.inc" + + return true; } else { return route_asset(req); } @@ -203,16 +214,26 @@ bool route_request( struct http_request* req ) return false; } - if( http_request_route( req, "/api/v1/" ) ) { - return route_mastodon_api( req ); - } else if( http_request_route( req, "/api/pleroma" ) ) { - return route_pleroma_api2( req ); - } else if( inner( req ) ) { - return true; + if( g_server->configured ) { + if( http_request_route( req, "/api/v1/" ) ) { + return route_mastodon_api( req ); + } else if( http_request_route( req, "/api/pleroma" ) ) { + return route_pleroma_api2( req ); + } else if( http_request_route( req, "/admin" ) ) { + return route_admin_request( req ); + } else if( inner( req ) ) { + return true; + } else { + return send_asset( req, "assets/soapbox/index.html" ); + } + + return false; } else { - return send_asset( req, "assets/soapbox/index.html" ); - } + if( http_request_route_term( req, "/" ) || http_request_route( req, "/?") ) { + return handle_admin_server_setup_wizard( req ); + } - return false; + return false; + } } diff --git a/src/controller/outbox.c b/src/controller/outbox.c index b73c456..0a04795 100644 --- a/src/controller/outbox.c +++ b/src/controller/outbox.c @@ -27,6 +27,7 @@ #include #include #include +#include static bool blacklisted( struct outbox_envelope* env ) { @@ -366,6 +367,8 @@ bool cleanup_box( const char* box ); extern bool terminate; void process_outbox() { + mkdir( "data/outbox", 0750 ); + while( !terminate ) { bool activity = false; activity |= process_pending(); diff --git a/src/ffdb b/src/ffdb index c57a8bb..b4edcf9 160000 --- a/src/ffdb +++ b/src/ffdb @@ -1 +1 @@ -Subproject commit c57a8bb19fedfb717ac0ae498b2e121f96d3507f +Subproject commit b4edcf9a332b0bc59b9f526889cd3059c77aaa5f diff --git a/src/main.c b/src/main.c index c4d97c3..0d00692 100644 --- a/src/main.c +++ b/src/main.c @@ -13,6 +13,7 @@ #include #include +#include bool terminate = false; @@ -23,6 +24,7 @@ void handle_ctrl_c(int) int main( int argc, char* argv[], char* envp[] ) { + srand( time(NULL) ); curl_global_init(CURL_GLOBAL_DEFAULT); model_init(); @@ -36,6 +38,7 @@ int main( int argc, char* argv[], char* envp[] ) int code = 0; if( g_server->section == -1 ) { printf( "Starting Apogee ActivityPub server...\n" ); + printf( "Using port %d for web server\n", g_server->http_settings.bind_port ); process_start_section(6); // Make sure we have a hidden service hostname diff --git a/src/model.c b/src/model.c index dd07d3a..6293697 100644 --- a/src/model.c +++ b/src/model.c @@ -2,8 +2,25 @@ #include "model/status.h" #include "model/peer.h" +#include + void model_init() { + mkdir( "data", 0750 ); + mkdir( "data/accounts", 0750 ); + mkdir( "data/activities", 0750 ); + mkdir( "data/bookmarks", 0750 ); + mkdir( "data/cache", 0750 ); + mkdir( "data/client_apps", 0750 ); + mkdir( "data/config", 0750 ); + mkdir( "data/crypto", 0750 ); + mkdir( "data/emoji", 0750 ); + mkdir( "data/indempotency", 0750 ); + mkdir( "data/media", 0750 ); + mkdir( "data/notices", 0750 ); + mkdir( "data/owner", 0750 ); + mkdir( "data/webfinger", 0750 ); + status_model_init(); peer_model_init(); } diff --git a/src/model/account.c b/src/model/account.c index 072624e..5298f7f 100644 --- a/src/model/account.c +++ b/src/model/account.c @@ -236,12 +236,17 @@ static struct account* account_load_from_id( int id, int recurse_limit ) return NULL; } + // Handle account migration if( a->replaced_by && a->replaced_by != a->id ) { int new_id = a->replaced_by; account_free(a); return account_load_from_id( new_id, recurse_limit - 1 ); } + // Sanity checks + if( !a->note ) { + a->note = strdup(""); + } if( !a->banner ) { a->banner = aformat( "https://%s/server/default-banner.blob", g_server->domain ); } diff --git a/src/model/client_app.c b/src/model/client_app.c index b492328..96278d4 100644 --- a/src/model/client_app.c +++ b/src/model/client_app.c @@ -18,6 +18,7 @@ static struct json_object_field client_app_layout[] = { JSON_FIELD_STRING( auth_code, false ), JSON_FIELD_STRING( access_token, false ), JSON_FIELD_STRING( redirect_uri, false ), + JSON_FIELD_DATETIME( last_used, false ), { .key = "secret", .offset = offsetof( struct client_app, client.secret ), @@ -69,6 +70,7 @@ struct client_app* client_app_new( const char* client_name ) app->auth_code = NULL; app->redirect_uri = NULL; app->access_token = NULL; + app->last_used = time(NULL); client_app_save(app); return app; diff --git a/src/model/client_app.h b/src/model/client_app.h index 29deff8..c7630d4 100644 --- a/src/model/client_app.h +++ b/src/model/client_app.h @@ -1,5 +1,7 @@ #pragma once +#include + struct client_app { struct { @@ -11,6 +13,7 @@ struct client_app char* redirect_uri; char* auth_code; char* access_token; + time_t last_used; // auth_expires }; diff --git a/src/model/crypto/keys.c b/src/model/crypto/keys.c index 2b3e0e8..e526070 100644 --- a/src/model/crypto/keys.c +++ b/src/model/crypto/keys.c @@ -66,6 +66,41 @@ bool crypto_keys_load_public( struct crypto_keys* keys, const char* filename ) return !!keys->pubkey; } +void crypt_keys_generate( struct crypto_keys* keys ) +{ + if( keys->privkey ) { + EVP_PKEY_free( keys->privkey ); + keys->privkey = NULL; + } + if( keys->pubkey ) { + EVP_PKEY_free( keys->pubkey ); + keys->pubkey = NULL; + } + + EVP_PKEY_CTX* pkey_context = EVP_PKEY_CTX_new_id( EVP_PKEY_RSA, NULL ); + EVP_PKEY_keygen_init( pkey_context ); + EVP_PKEY_CTX_set_rsa_keygen_bits( pkey_context, 2048 ); + EVP_PKEY_keygen( pkey_context, &keys->privkey ); + + keys->pubkey = keys->privkey; + EVP_PKEY_up_ref(keys->pubkey); +} +void crypto_keys_save_public( struct crypto_keys* keys, const char* filename ) +{ + FILE* f = fopen( filename, "w"); + if( !f ) { return; } + + PEM_write_PUBKEY( f, keys->pubkey ); + fclose(f); +} +void crypto_keys_save_private( struct crypto_keys* keys, const char* filename ) +{ + FILE* f = fopen( filename, "w"); + if( !f ) { return; } + + PEM_write_PrivateKey( f, keys->privkey, NULL, NULL, 0, NULL, NULL ); + fclose(f); +} char* crypto_keys_sign( struct crypto_keys* keys, void* data, unsigned int size ) { diff --git a/src/model/crypto/keys.h b/src/model/crypto/keys.h index d8fb63c..af967dc 100644 --- a/src/model/crypto/keys.h +++ b/src/model/crypto/keys.h @@ -10,6 +10,9 @@ void crypto_keys_free( struct crypto_keys* keys ); bool crypto_keys_load_public ( struct crypto_keys* keys, const char* filename ); bool crypto_keys_load_private( struct crypto_keys* keys, const char* filename ); +void crypt_keys_generate( struct crypto_keys* keys ); +void crypto_keys_save_public( struct crypto_keys* keys, const char* filename ); +void crypto_keys_save_private( struct crypto_keys* keys, const char* filename ); char* crypto_keys_sign( struct crypto_keys* keys, void* data, unsigned int size ); bool crypto_keys_verify( struct crypto_keys* keys, void* data, unsigned int size, char* signature ); diff --git a/src/model/owner.c b/src/model/owner.c index 2622cdd..0d576e2 100644 --- a/src/model/owner.c +++ b/src/model/owner.c @@ -3,6 +3,7 @@ #include "json/json.h" #include "json/layout.h" #include "sha256/sha256.h" +#include "form.h" #include #include @@ -32,10 +33,7 @@ struct owner* owner_new() { struct owner* o = allocate(sizeof(struct owner)); - if( !json_read_object_layout_from_file( "data/owner.json", owner_layout, o ) ) { - owner_free(o); - return NULL; - } + json_read_object_layout_from_file( "data/owner.json", owner_layout, o ); return o; } @@ -46,15 +44,24 @@ void owner_free( struct owner* o ) free(o->password_hash); free(o); } +void owner_save( struct owner* o ) +{ + json_write_object_layout_to_file( "data/owner.json", "\t", owner_layout, o ); +} bool owner_check_password( struct owner* o, const char* passwd ) { char buffer[512]; snprintf( buffer, 512, "%s:%s", o->password_salt, passwd ); + printf( "passwd='%s'\n", passwd ); + char hash[65] = ""; sha256_easy_hash_hex( buffer, strlen(buffer), hash ); + printf( "hash=%s\n", hash ); + printf( "expt=%s\n", o->password_hash ); + return 0 == strcmp(hash,o->password_hash); } @@ -64,6 +71,7 @@ void owner_set_password( struct owner* o, const char* passwd ) free(o->password_hash); char* new_salt = o->password_salt = malloc(65); + memset(new_salt,0,65); for( int i = 0; i < 64; ++i ) { new_salt[i] = 'a' + (rand()%26); } @@ -73,6 +81,10 @@ void owner_set_password( struct owner* o, const char* passwd ) snprintf( buffer, 512, "%s:%s", new_salt, passwd ); char* new_hash = o->password_hash = malloc(65); + memset(new_hash,0,65); sha256_easy_hash_hex( buffer, strlen(buffer), new_hash ); + + o->password_hash = new_hash; + o->password_salt = new_salt; } diff --git a/src/model/owner.h b/src/model/owner.h index 1dfe20d..320a52b 100644 --- a/src/model/owner.h +++ b/src/model/owner.h @@ -15,4 +15,5 @@ void owner_free( struct owner* ); bool owner_check_password( struct owner* o, const char* passwd ); void owner_set_password( struct owner* o, const char* passwd ); +struct form_pull_parser; diff --git a/src/model/server.c b/src/model/server.c index 12d168a..48567bf 100644 --- a/src/model/server.c +++ b/src/model/server.c @@ -1,17 +1,20 @@ #include "server.h" -#include "json/layout.h" - #include "controller/cli.h" #include "process.h" +// Submodules +#include "json/layout.h" +#include "form.h" + +// Platform libraries #include #include #include #define OBJ_TYPE struct app_args static struct json_object_field app_args_layout[] = { - JSON_FIELD_STRING( domain, true ), + JSON_FIELD_STRING( icann_domain, false ), JSON_FIELD_STRING( user_agent, false ), { .key = "addr", @@ -29,6 +32,10 @@ static struct json_object_field app_args_layout[] = { JSON_FIELD_INTEGER( outbox_discard_limit, false ), JSON_FIELD_BOOL( develop, false ), + JSON_FIELD_BOOL( disabled, false ), + JSON_FIELD_BOOL( configured, false ), + + JSON_FIELD_INTEGER( setup_wizard_step, false ), JSON_FIELD_END, }; #undef OBJ_TYPE @@ -42,6 +49,9 @@ void app_args_refresh_tor_hidden_service() g_server->tor_hidden_service = NULL; FILE* f = fopen("data/tor/hidden_service/hostname","r"); + if( !f ) { + return; + } size_t n; getline( &g_server->tor_hidden_service, &n, f ); n = strlen( g_server->tor_hidden_service ); @@ -50,28 +60,42 @@ void app_args_refresh_tor_hidden_service() } fclose(f); } -struct app_args* app_args_new( int argc, char** argv ) +void app_args_load() { - struct app_args* args = (struct app_args*)malloc( sizeof(struct app_args) ); - memset(args,0,sizeof(*args)); - if( !args ) { return NULL; } + g_server->http_settings.bind_port = 9000 + ( rand() % 41000 ); + g_server->http_settings.bind_address = strdup("0.0.0.0"); + g_server->tor_socks_port = ( rand() % 41000 ) + 9000; + g_server->section = -1; + g_server->outbox_discard_limit = 5; + g_server->user_agent = strdup( "Apogee/0.1" ); + g_server->disabled = true; - g_server = args; + json_read_object_layout_from_file( "data/server.json", app_args_layout, g_server ); - args->http_settings.bind_port = 9053; - args->http_settings.bind_address = strdup("0.0.0.0"); - args->tor_socks_port = 9123; - args->section = -1; - args->outbox_discard_limit = 5; - args->user_agent = strdup( "Apogee/0.1" ); + app_args_refresh_tor_hidden_service(); - json_read_object_layout_from_file( "data/server.json", app_args_layout, args ); + if( g_server->icann_domain ) { + g_server->domain = strdup(g_server->icann_domain); + } else if( g_server->tor_hidden_service ) { + g_server->domain = strdup(g_server->tor_hidden_service); + } - app_args_refresh_tor_hidden_service(); + printf( "Using domain: %s\n", g_server->domain ); +} +struct app_args* app_args_new( int argc, char** argv ) +{ + if( !g_server ) { + struct app_args* args = (struct app_args*)malloc( sizeof(struct app_args) ); + memset(args,0,sizeof(*args)); + if( !args ) { return NULL; } + + g_server = args; + } + + app_args_load(); if( ( argc > 1 ) && ( 0 != strncmp(argv[1],"--",2) ) ) { handle_command( argv, argc ); - free(args); return NULL; } @@ -79,16 +103,22 @@ struct app_args* app_args_new( int argc, char** argv ) const char* arg = argv[i]; // Debug flag - if( 0 == strcmp(argv[i],"--debug") ) { args->debug = true; goto next_arg; } + if( 0 == strcmp(argv[i],"--debug") ) { g_server->debug = true; goto next_arg; } // Sections by number - if( sscanf(arg,"--section=%d",&args->section) ) { goto next_arg; } + if( sscanf(arg,"--section=%d",&g_server->section) ) { goto next_arg; } + + // Override server port + if( sscanf(arg,"--listen=%d",&g_server->http_settings.bind_port) ) { + printf( "Listening on port %d\n", g_server->http_settings.bind_port ); + goto next_arg; + } // Sections by name if( ( argv[i][0] == '-' ) && ( argv[i][1] == '-' ) ) { for( int i = 0; i <= process_get_max_section(); ++i ) { if( strcmp( &argv[i][2], process_get_section_name(i) ) ) { - args->section = i; + g_server->section = i; goto next_arg; } } @@ -96,27 +126,74 @@ struct app_args* app_args_new( int argc, char** argv ) // Development if( 0 == strcmp(argv[i],"--devel") ) { - args->section = 100; + g_server->section = 100; goto next_arg; } // Unknown argument printf( "Unknown argument: %s\n", argv[i] ); - free(args); return NULL; next_arg:; } - return args; + return g_server; } void app_args_release( struct app_args* args ) { free(args->http_settings.bind_address); free(args->domain); + free(args->icann_domain); free(args->tor_hidden_service); free(args->user_agent); free(args); } +void app_args_save() +{ + // Save to disk + json_write_object_layout_to_file( "data/server.json", "\t", app_args_layout, g_server ); +} +void app_args_load_from_form( struct app_args* args, struct form_parser* fp ) +{ + char* key = NULL; + while( (key=form_pull_parser_read_key(fp)) != NULL ) { + for( struct json_object_field* jof = app_args_layout; jof->key; jof += 1 ) { + intptr_t field_offset = (intptr_t)args + jof->offset; + + if( strcmp(jof->key, key) == 0 ) { + if( jof->type == &json_field_string ) { + const char* value = form_pull_parser_read_value(fp); + char** field = (char**)field_offset; + + printf( "%s: %s\n", jof->key, value ); + free( *field ); + *field = NULL; + + if( value ) { + *field = strdup(value); + } + } else if( jof->type == &json_field_integer ) { + const char* value = form_pull_parser_read_value(fp); + printf( "%s: %s\n", jof->key, value ); + + int* field = (int*)field_offset; + sscanf( value, "%d", field ); + } else if( jof->type == &json_field_bool ) { + const char* value = form_pull_parser_read_value(fp); + printf( "%s: %s\n", jof->key, value ); + + bool* field = (bool*)field_offset; + if( strcmp(value,"on") == 0 ) { + *field = true; + } else { + *field = false; + } + } else { + printf( "Unknown type for field %s\n", jof->key ); + } + } + } + } +} diff --git a/src/model/server.h b/src/model/server.h index e227d79..f3d26f6 100644 --- a/src/model/server.h +++ b/src/model/server.h @@ -11,6 +11,7 @@ struct app_args struct http_server_args http_settings; //char* addr; char* domain; + char* icann_domain; char* tor_hidden_service; char* user_agent; bool debug; @@ -21,9 +22,17 @@ struct app_args int outbox_discard_limit; bool develop; + bool disabled; + bool configured; + int setup_wizard_step; }; struct app_args* app_args_new( int argc, char** argv ); +void app_args_load(); void app_args_release( struct app_args* args ); +void app_args_save(); + +struct form_parser; +void app_args_load_from_form( struct app_args* args, struct form_parser* fp ); extern struct app_args* g_server; diff --git a/src/process.c b/src/process.c index abb8367..7b55540 100644 --- a/src/process.c +++ b/src/process.c @@ -130,6 +130,8 @@ static void redirect_io( int section ) const char* section_name_str = process_get_section_name(section); if( !section_name_str ) { return; } + mkdir( "data/logs", 0750 ); + char filename[512]; snprintf( filename,512, "data/logs/%s.log", section_name_str ); diff --git a/src/util b/src/util index e133427..74376d4 160000 --- a/src/util +++ b/src/util @@ -1 +1 @@ -Subproject commit e133427bb7da0224fc365252406024c207418863 +Subproject commit 74376d45529aa0b5e845cddd00005ee2aedcc66f diff --git a/src/view/admin/owner-setup.html.template b/src/view/admin/owner-setup.html.template new file mode 100644 index 0000000..9192198 --- /dev/null +++ b/src/view/admin/owner-setup.html.template @@ -0,0 +1,34 @@ +%(/* + vim: filetype=html +*/) + +

Welcome to Apogee

+

+Apogee is a single-user Activity Pub federated server intended for self-hosting. +

+ +

Account Settings

+
+ + + + + + + + + + + + + + + + +
Username:
Password:
Confirm Password:
+ +
+ +
+ + diff --git a/src/view/admin/server-setup.html.template b/src/view/admin/server-setup.html.template new file mode 100644 index 0000000..44d5825 --- /dev/null +++ b/src/view/admin/server-setup.html.template @@ -0,0 +1,77 @@ +%(/* + vim: filetype=html +*/) + +

Welcome to Apogee

+

+Apogee is a single-user Activity Pub federated server intended for self-hosting. +

+ +

Server Settings

+
+

HTTP

+ + + + + + + + + + + + + + + + + + + + + +
ICANN Domain Name:
Listen Address:
Listen Port:
User Agent:
+ +

Tor

+ + + + + + + + + + + +
Disable:disable_tor) }/>
SOCKS Port:
+ +

Other

+ + + + + + + + + + + + + + + + + + + + + +
Outbox Discard Limit:
Debug Mode:debug) }/>Greatly increases server verbosity
Developer Mode:develop) }/>Change server behavior to suit developers
Server Disabled:disabled ) }/>Turn the entire server off
+
+ +
+ + diff --git a/src/view/soapbox.json.template b/src/view/soapbox.json.template new file mode 100644 index 0000000..1c2fc90 --- /dev/null +++ b/src/view/soapbox.json.template @@ -0,0 +1,33 @@ +{ + "logo":"/instance/images/logo.png", + "brandColor": "#0482d8", + "promoPanel":{ + "items":[ + { + "icon": "area-chart", + "text": "Site stats", + "url": "https://fediverse.network/%s{ g_server->domain }" + }, + { + "icon": "admin", + "text": "Server Settings", + "url": "/admin/server-settings" + } + ] + }, + "defaultSettings": { + "autoPlayGif": false, + "themeMode": "light" + }, + "copyright": "♡2020. Copying is an act of love. Please copy and share.", + "navlinks": { + "homeFooter":[ + { "title": "About", "url": "/about" }, + { "title": "Terms of Service", "url": "/about/tos" }, + { "title": "Privacy Policy", "url": "/about/privacy" }, + { "title": "DMCA", "url": "/about/dmca" }, + { "title": "Source Code", "url": "/about#opensource" } + ] + }, + "allowedEmoji": ["🤔", "😂", "😭", "😡", "🥰", "👍🏼", "👎🏼", "🔥", "😩", "🍆"] +}