Implement server setup wizard

master
teknomunk 5 months ago
parent ce178cd5a9
commit 9d99318265

1
.gitignore vendored

@ -13,3 +13,4 @@ apogee
apogee.debug
/*.log
/*.json
obj/

@ -16,13 +16,17 @@ 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)
* Globaly resolvable domain name
# Installation Instructions
# Source Installation Instructions
Ensure you have system packages for the dependencies installed, then run the following code in
a terminal window:
@ -47,6 +51,6 @@ 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://${hostname-or-ip}:${port}/, then fill in the
port is used. Open a browser and navigate to http://${hostnameOrIPAddress}:${port}/, then fill in the
required information.

@ -2,14 +2,22 @@
// Model
#include "src/model/server.h"
#include "src/model/owner.h"
#include "src/model/account.h"
// View
// Controller
#include "src/controller/api/client_apps.h"
// Submodules
#include "form.h"
#include "http/server/request.h"
// Platform Headers
#include <string.h>
#include <stdlib.h>
const char* view_checkbox( bool value )
{
return value ? "checked" : "";
@ -33,13 +41,132 @@ bool route_admin_request( struct http_request* req )
return false;
}
// Route: /admin/server-setup
// Special: / (when server hasn't been configured)
bool handle_admin_server_setup( struct http_request* req )
// 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;
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);
}
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));
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/server-setup.html.inc"
#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;
}

@ -6,4 +6,5 @@ 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 );

@ -170,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" ) ) {
@ -211,20 +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( http_request_route( req, "/admin" ) ) {
return route_admin_request( req );
} else if( inner( req ) ) {
return true;
} else if( http_request_route_term( req, "/" ) && !g_server->configured ) {
return handle_admin_server_setup( req );
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;
}
}

@ -3,6 +3,7 @@
#include "json/json.h"
#include "json/layout.h"
#include "sha256/sha256.h"
#include "form.h"
#include <stdlib.h>
#include <stdio.h>
@ -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;
}

@ -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;

@ -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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#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,8 +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( enabled, false ),
JSON_FIELD_BOOL( disabled, false ),
JSON_FIELD_BOOL( configured, false ),
JSON_FIELD_INTEGER( setup_wizard_step, false ),
JSON_FIELD_END,
};
#undef OBJ_TYPE
@ -55,28 +60,40 @@ 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;
json_read_object_layout_from_file( "data/server.json", app_args_layout, g_server );
g_server = args;
app_args_refresh_tor_hidden_service();
args->http_settings.bind_port = 9000 + ( rand() % 41000 );
args->http_settings.bind_address = strdup("0.0.0.0");
args->tor_socks_port = ( rand() % 41000 ) + 9000;
args->section = -1;
args->outbox_discard_limit = 5;
args->user_agent = strdup( "Apogee/0.1" );
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);
}
}
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; }
json_read_object_layout_from_file( "data/server.json", app_args_layout, args );
g_server = args;
}
app_args_refresh_tor_hidden_service();
app_args_load();
if( ( argc > 1 ) && ( 0 != strncmp(argv[1],"--",2) ) ) {
handle_command( argv, argc );
free(args);
return NULL;
}
@ -84,14 +101,14 @@ 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",&args->http_settings.bind_port) ) {
printf( "Listening on port %d\n", args->http_settings.bind_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;
}
@ -99,7 +116,7 @@ struct app_args* app_args_new( int argc, char** argv )
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;
}
}
@ -107,32 +124,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()
{
// TODO: save server settings to disk
// 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 );
}
}
}
}
}

@ -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,12 +22,17 @@ struct app_args
int outbox_discard_limit;
bool develop;
bool enabled;
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;

@ -0,0 +1,34 @@
<html>%(/*
vim: filetype=html
*/)
<body>
<h1>Welcome to Apogee</h1>
<p>
Apogee is a single-user Activity Pub federated server intended for self-hosting.
</p>
<h2>Account Settings</h2>
<form method="POST">
<table width='100%%'>
<tr>
<td width="15%%"><b>Username:</b></td>
<td><input name="handle" type="text" /></td>
<td></td>
</tr>
<tr>
<td><b>Password:</b></td>
<td><input name="password" type="password" /></td>
<td></td>
</tr>
<tr>
<td><b>Confirm Password:</b></td>
<td><input name="confirm" type="password" /></td>
<td></td>
</tr>
</table>
<br/>
<input type="submit" />
</form>
</body>
</html>

@ -8,13 +8,14 @@ Apogee is a single-user Activity Pub federated server intended for self-hosting.
</p>
<h2>Server Settings</h2>
<form method="POST" action="/admin/">
<form method="POST">
<h3>HTTP</h3>
<table width='100%%'>
<tr>
<td width="15%%"><b>Domain Name:</b></td>
<td><input name="domain" type="text" value="%s{ g_server->domain ? g_server->domain : "" }"/></td>
<td width="15%%"><b>ICANN Domain Name:</b></td>
<td><input name="icann_domain" type="text" value="%s{ g_server->icann_domain ? g_server->icann_domain : "" }"/></td>
<td></td>
</tr>
<tr>
<td><b>Listen Address:</b></td>
<td><input name="address" type="text" value="%s{ g_server->http_settings.bind_address ? g_server->http_settings.bind_address : "0.0.0.0" }"/></td>
@ -64,8 +65,8 @@ Apogee is a single-user Activity Pub federated server intended for self-hosting.
<td>Change server behavior to suit developers</td>
</tr>
<tr>
<td><b>Server Enabled:</b></td>
<td><input name="enabled" type="checkbox" %s{ view_checkbox( g_server->enabled ) }/></td>
<td><b>Server Disabled:</b></td>
<td><input name="disabled" type="checkbox" %s{ view_checkbox( g_server->disabled ) }/></td>
<td>Turn the entire server off</td>
</tr>
</table>

Loading…
Cancel
Save